Add unit tests for public interfaces.

This refactors the code to unit test the public interfaces of the
ImageLoader class. It also fixes a bug in GetComponentVersion, caught by
the new tests.

BUG=chromium:630421
TEST=FEATURES="test" emerge-${BOARD} imageloader, and run the client

Change-Id: I62657ee9127586389677bf5a3b1a65766ec2a038
Reviewed-on: https://chromium-review.googlesource.com/363520
Commit-Ready: Greg Kerr <kerrnel@chromium.org>
Tested-by: Greg Kerr <kerrnel@chromium.org>
Reviewed-by: Ricky Zhou <rickyz@chromium.org>
diff --git a/.presubmitignore b/.presubmitignore
index a09690d..28815b7 100644
--- a/.presubmitignore
+++ b/.presubmitignore
@@ -1,2 +1,4 @@
 test/22.0.0.158_chromeos_intel64_PepperFlashPlayer/params
 test/22.0.0.158_chromeos_intel64_PepperFlashPlayer/hash
+test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/params
+test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/hash
diff --git a/imageloadclient-glue.xml b/imageloadclient-glue.xml
index 68058d2..d7cbcd5 100644
--- a/imageloadclient-glue.xml
+++ b/imageloadclient-glue.xml
@@ -20,9 +20,5 @@
       <arg type="s" name="name" direction="in" />
       <arg type="s" name="mount_point" direction="out" />
     </method>
-    <method name="UnloadComponent">
-      <arg type="s" name="name" direction="in" />
-      <arg type="b" name="success" direction="out" />
-    </method>
   </interface>
 </node>
diff --git a/imageloadclient.cc b/imageloadclient.cc
index 42620c1..a3c3de6 100644
--- a/imageloadclient.cc
+++ b/imageloadclient.cc
@@ -51,16 +51,6 @@
   }
 }
 
-void ImageLoadClient::UnloadComponentCallback(const bool& success,
-                                              const ::DBus::Error& err,
-                                              void*) {
-  if (success) {
-    LOG(INFO) << "Success.";
-  } else {
-    LOG(INFO) << "Failure.";
-  }
-}
-
 namespace {
 
 void *TestCalls(void *arg) {
@@ -86,9 +76,6 @@
     } else if (inp == "lc") {  // LoadComponent
       std::cin >> name;
       client->LoadComponentAsync(name, NULL);
-    } else if (inp == "uc") {  // UnloadComponent
-      std::cin >> name;
-      client->UnloadComponentAsync(name, NULL);
     }
   }
   return NULL;
diff --git a/imageloader-glue.xml b/imageloader-glue.xml
index 68058d2..d7cbcd5 100644
--- a/imageloader-glue.xml
+++ b/imageloader-glue.xml
@@ -20,9 +20,5 @@
       <arg type="s" name="name" direction="in" />
       <arg type="s" name="mount_point" direction="out" />
     </method>
-    <method name="UnloadComponent">
-      <arg type="s" name="name" direction="in" />
-      <arg type="b" name="success" direction="out" />
-    </method>
   </interface>
 </node>
diff --git a/imageloader.gyp b/imageloader.gyp
index cde30dd..cbced4b 100644
--- a/imageloader.gyp
+++ b/imageloader.gyp
@@ -54,7 +54,7 @@
         'libimageloader_common',
       ],
       'sources': [
-        'imageloader.cc',
+        'imageloader_impl.cc',
         'imageloader.h',
       ],
     },
diff --git a/imageloader.h b/imageloader.h
index fd962ae..865b3b9 100644
--- a/imageloader.h
+++ b/imageloader.h
@@ -4,15 +4,14 @@
 #ifndef IMAGELOADER_IMAGELOADER_H_
 #define IMAGELOADER_IMAGELOADER_H_
 
-#include <map>
 #include <string>
-#include <utility>
 
-#include <base/files/file_path.h>
-#include <base/gtest_prod_util.h>
+#include <base/macros.h>
 #include <dbus-c++/dbus.h>
 
+#include "imageloader_common.h"
 #include "imageloader-glue.h"
+#include "imageloader_impl.h"
 
 namespace imageloader {
 
@@ -23,73 +22,30 @@
                     public DBus::ObjectAdaptor {
  public:
   // Instantiate a D-Bus Helper Instance
-  explicit ImageLoader(DBus::Connection* conn);
+  ImageLoader(DBus::Connection* conn, const ImageLoaderConfig& config)
+      : DBus::ObjectAdaptor(*conn, kImageLoaderPath), impl_(config) {}
 
   // Register a component.
   bool RegisterComponent(const std::string& name, const std::string& version,
                          const std::string& component_folder_abs_path,
-                         ::DBus::Error& err);
+                         ::DBus::Error& err) {
+    return impl_.RegisterComponent(name, version, component_folder_abs_path);
+  }
 
   // Get component version given component name.
-  std::string GetComponentVersion(const std::string& name, ::DBus::Error& err);
+  std::string GetComponentVersion(const std::string& name, ::DBus::Error& err) {
+    return impl_.GetComponentVersion(name);
+  }
 
   // Load the specified component.
-  std::string LoadComponent(const std::string& name, ::DBus::Error& err);
-  std::string LoadComponentUtil(const std::string& name);
-
-  // Unload the specified component.
-  bool UnloadComponent(const std::string& name, ::DBus::Error& err);
-  bool UnloadComponentUtil(const std::string& name);
+  std::string LoadComponent(const std::string& name, ::DBus::Error& err) {
+    return impl_.LoadComponent(name);
+  }
 
  private:
-  struct Manifest {
-    int manifest_version;
-    std::vector<uint8_t> image_sha256;
-    std::vector<uint8_t> params_sha256;
-    std::string version;
-  };
+  ImageLoaderImpl impl_;
 
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ECVerify);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ManifestFingerPrint);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyValidComponent);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyComponentWithBadManifest);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyValidImage);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyInvalidImage);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyInvalidHash);
-  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ParseManifest);
-
-  // Verify the data with the RSA (PKCS #1 v1.5) signature.
-  static bool ECVerify(const base::StringPiece data,
-                       const base::StringPiece sig);
-
-  // Copy the component directory from a user controlled location to an
-  // imageloader controlled location. Do not copy unless it verifies.
-  static bool CopyComponentDirectory(const base::FilePath& component_path,
-                                     const base::FilePath& destination_folder,
-                                     const std::string& version);
-  // Check the string contents to see if it matches the format of a
-  // manifest.fingerprint file.
-  static bool IsValidFingerprintFile(const std::string& contents);
-  // Verify the imageloader.json manifest file and parse the file information
-  // out of it.
-  static bool VerifyAndParseManifest(const std::string& manifest_str,
-                                     const std::string& signature,
-                                     Manifest* manifest);
-  // Copies files over and checks their hash in the process. The copy fails if
-  // the hashes do not match.
-  static bool CopyAndHashFile(const base::FilePath& src_path,
-                              const base::FilePath& dest_path,
-                              const std::vector<uint8_t>& known_hash);
-  // Check if the client created a manifest.fingerprint, and preserve it.
-  static bool CopyFingerprintFile(const base::FilePath& src,
-                                  const base::FilePath& dest);
-
-  // "mounts" keeps track of what has been mounted.
-  // mounts = (name, (mount_point, device_path))
-  std::map<std::string, std::pair<base::FilePath, base::FilePath>> mounts;
-  // "reg" keeps track of registered components.
-  // reg = (name, (version, fs_image_abs_path))
-  std::map<std::string, std::pair<std::string, base::FilePath>> reg;
+  DISALLOW_COPY_AND_ASSIGN(ImageLoader);
 };
 
 }  // namespace imageloader
diff --git a/imageloader.cc b/imageloader_impl.cc
similarity index 66%
rename from imageloader.cc
rename to imageloader_impl.cc
index 1e70ae8..8fa572e 100644
--- a/imageloader.cc
+++ b/imageloader_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "imageloader.h"
+#include "imageloader_impl.h"
 
 #include <fcntl.h>
 #include <linux/loop.h>
@@ -44,13 +44,6 @@
 
 using imageloader::kBadResult;
 
-// Generate a good enough (unique) mount point.
-base::FilePath GenerateMountPoint(const char prefix[]) {
-  return base::FilePath(prefix + base::GenerateGUID());
-}
-
-// The path where the components are stored on the device.
-const char kComponentsPath[] = "/mnt/stateful_partition/encrypted/imageloader";
 // The name of the fingerprint file.
 const char kFingerprintName[] = "manifest.fingerprint";
 // The name of the imageloader manifest file.
@@ -78,22 +71,9 @@
 // The maximum size of any file to read into memory.
 const size_t kMaximumFilesize = 4096 * 10;
 
-// TODO(kerrnel): Switch to the prod keys before shipping this feature.
-const uint8_t kDevPublicKey[] =  {
-  0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
-  0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
-  0x42, 0x00, 0x04, 0x7a, 0xaa, 0x2b, 0xf9, 0x3d, 0x7a, 0xbe, 0x35, 0x9a,
-  0xfc, 0x9f, 0x39, 0x2d, 0x2d, 0x37, 0x07, 0xd4, 0x19, 0x67, 0x67, 0x30,
-  0xbb, 0x5c, 0x74, 0x22, 0xd5, 0x02, 0x07, 0xaf, 0x6b, 0x12, 0x9d, 0x12,
-  0xf0, 0x34, 0xfd, 0x1a, 0x7f, 0x02, 0xd8, 0x46, 0x2b, 0x25, 0xca, 0xa0,
-  0x6e, 0x2b, 0x54, 0x41, 0xee, 0x92, 0xa2, 0x0f, 0xa2, 0x2a, 0xc0, 0x30,
-  0xa6, 0x8c, 0xd1, 0x16, 0x0a, 0x48, 0xca
-};
-
-bool AssertComponentDirPerms() {
+bool AssertComponentDirPerms(const base::FilePath& path) {
   int mode;
-  base::FilePath components_dir(kComponentsPath);
-  if (!GetPosixFilePermissions(components_dir, &mode)) return false;
+  if (!GetPosixFilePermissions(path, &mode)) return false;
   return mode == kComponentDirPerms;
 }
 
@@ -123,16 +103,14 @@
 // CRX. The file is used for delta updates. Since Chrome OS doesn't rely on it
 // for security of the disk image, we are fine with sanity checking the contents
 // and then preserving the unsigned file.
-// static
-bool ImageLoader::IsValidFingerprintFile(const std::string& contents) {
+bool ImageLoaderImpl::IsValidFingerprintFile(const std::string& contents) {
   return contents.size() <= 256 &&
          std::find_if_not(contents.begin(), contents.end(), [](char ch) {
            return base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch) || ch == '.';
          }) == contents.end();
 }
 
-// static
-bool ImageLoader::CopyFingerprintFile(const base::FilePath& src,
+bool ImageLoaderImpl::CopyFingerprintFile(const base::FilePath& src,
                                       const base::FilePath& dest) {
   base::FilePath fingerprint_path = src.Append(kFingerprintName);
   if (base::PathExists(fingerprint_path)) {
@@ -151,15 +129,15 @@
   return true;
 }
 
-bool ImageLoader::CopyAndHashFile(const base::FilePath& src_path,
-                                  const base::FilePath& dest_path,
-                                  const std::vector<uint8_t>& expected_hash) {
+bool ImageLoaderImpl::CopyAndHashFile(
+    const base::FilePath& src_path, const base::FilePath& dest_path,
+    const std::vector<uint8_t>& expected_hash) {
   base::File file(src_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
   if (!file.IsValid()) return false;
 
-  base::ScopedFD dest(HANDLE_EINTR(open(dest_path.value().c_str(),
-                                        O_CREAT | O_WRONLY | O_EXCL,
-                                        kComponentFilePerms)));
+  base::ScopedFD dest(
+      HANDLE_EINTR(open(dest_path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL,
+                        kComponentFilePerms)));
   if (!dest.is_valid()) return false;
 
   base::File out_file(dest.release());
@@ -171,7 +149,7 @@
 
   size_t bytes_read = 0;
   int rv = 0;
-  char  buf[4096];
+  char buf[4096];
   do {
     int remaining = size - bytes_read;
     int bytes_to_read =
@@ -184,8 +162,7 @@
     bytes_read += rv;
     sha256->Update(buf, rv);
 
-    if (out_file.WriteAtCurrentPos(buf, rv) != rv)
-      return false;
+    if (out_file.WriteAtCurrentPos(buf, rv) != rv) return false;
   } while (bytes_read <= size);
 
   if (bytes_read != size) return false;
@@ -200,8 +177,7 @@
   return true;
 }
 
-// static
-bool ImageLoader::VerifyAndParseManifest(const std::string& manifest_contents,
+bool ImageLoaderImpl::VerifyAndParseManifest(const std::string& manifest_contents,
                                          const std::string& signature,
                                          Manifest* manifest) {
   // Verify the manifest before trusting any of its contents.
@@ -271,8 +247,7 @@
   return true;
 }
 
-// static
-bool ImageLoader::CopyComponentDirectory(
+bool ImageLoaderImpl::CopyComponentDirectory(
     const base::FilePath& component_path,
     const base::FilePath& destination_folder, const std::string& version) {
   if (mkdir(destination_folder.value().c_str(), kComponentDirPerms) != 0) {
@@ -339,16 +314,16 @@
   return true;
 }
 
-// static
-bool ImageLoader::ECVerify(const base::StringPiece data,
-                           const base::StringPiece sig) {
+bool ImageLoaderImpl::ECVerify(const base::StringPiece data,
+                                  const base::StringPiece sig) {
   crypto::SignatureVerifier verifier;
 
-  if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256,
-                           reinterpret_cast<const uint8_t*>(sig.data()),
-                           base::checked_cast<int>(sig.size()),
-                           reinterpret_cast<const uint8_t*>(kDevPublicKey),
-                           base::checked_cast<int>(sizeof(kDevPublicKey)))) {
+  if (!verifier.VerifyInit(
+          crypto::SignatureVerifier::ECDSA_SHA256,
+          reinterpret_cast<const uint8_t*>(sig.data()),
+          base::checked_cast<int>(sig.size()),
+          config_.key.data(),
+          base::checked_cast<int>(config_.key.size()))) {
     return false;
   }
 
@@ -359,98 +334,15 @@
 }
 
 // Mount component at location generated.
-std::string ImageLoader::LoadComponentUtil(const std::string& name) {
-  base::FilePath mount_point = GenerateMountPoint("/mnt/");
-  // Is this somehow taken up by any other name or mount?
-  for (auto it = mounts.begin(); it != mounts.end(); ++it) {
-    if ((it->second).first == mount_point) {
-      return kBadResult;
-    }
-  }
-  if (PathExists(mount_point)) {
-    LOG(INFO) << "Generated mount_point is already stat-able : "
-              << mount_point.value();
-    return kBadResult;
-  }
-  // The mount point is not yet taken, so go ahead.
-  base::ScopedFD loopctl_fd(open("/dev/loop-control", O_RDONLY | O_CLOEXEC));
-  if (!loopctl_fd.is_valid()) {
-    PLOG(ERROR) << "loopctl_fd";
-    return kBadResult;
-  }
-  int device_free_number = ioctl(loopctl_fd.get(), LOOP_CTL_GET_FREE);
-  if (device_free_number < 0) {
-    PLOG(ERROR) << "ioctl : LOOP_CTL_GET_FREE";
-    return kBadResult;
-  }
-  std::ostringstream device_path;
-  device_path << "/dev/loop" << device_free_number;
-  base::ScopedFD device_path_fd(
-      open(device_path.str().c_str(), O_RDONLY | O_CLOEXEC));
-  if (!device_path_fd.is_valid()) {
-    PLOG(ERROR) << "device_path_fd";
-    return kBadResult;
-  }
-  base::ScopedFD fs_image_fd(
-      open(reg[name].second.value().c_str(), O_RDONLY | O_CLOEXEC));
-  if (!fs_image_fd.is_valid()) {
-    PLOG(ERROR) << "fs_image_fd";
-    return kBadResult;
-  }
-  if (ioctl(device_path_fd.get(), LOOP_SET_FD, fs_image_fd.get()) < 0) {
-    PLOG(ERROR) << "ioctl: LOOP_SET_FD";
-    return kBadResult;
-  }
-  if (!base::CreateDirectory(mount_point)) {
-    PLOG(ERROR) << "CreateDirectory : " << mount_point.value();
-    ioctl(device_path_fd.get(), LOOP_CLR_FD, 0);
-    return kBadResult;
-  }
-  if (mount(device_path.str().c_str(), mount_point.value().c_str(), "squashfs",
-            MS_RDONLY | MS_NOSUID | MS_NODEV, "") < 0) {
-    PLOG(ERROR) << "mount";
-    ioctl(device_path_fd.get(), LOOP_CLR_FD, 0);
-    return kBadResult;
-  }
-  mounts[name] = std::make_pair(mount_point, base::FilePath(device_path.str()));
-  return mount_point.value();
+std::string ImageLoaderImpl::LoadComponentUtil(const std::string& name) {
+  // Not implemented yet.
+  return kBadResult;
 }
 
-// Unmount the given component.
-bool ImageLoader::UnloadComponentUtil(const std::string& name) {
-  std::string device_path = mounts[name].second.value();
-  if (umount(mounts[name].first.value().c_str()) < 0) {
-    PLOG(ERROR) << "umount";
-    return false;
-  }
-  const base::FilePath fp_mount_point(mounts[name].first);
-  if (!DeleteFile(fp_mount_point, false)) {
-    PLOG(ERROR) << "DeleteFile : " << fp_mount_point.value();
-    return false;
-  }
-  base::ScopedFD device_path_fd(
-      open(device_path.c_str(), O_RDONLY | O_CLOEXEC));
-  if (!device_path_fd.is_valid()) {
-    PLOG(ERROR) << "device_path_fd";
-    return false;
-  }
-  if (ioctl(device_path_fd.get(), LOOP_CLR_FD, 0) < 0) {
-    PLOG(ERROR) << "ioctl: LOOP_CLR_FD";
-    return false;
-  }
-  mounts.erase(mounts.find(name));
-  return true;
-}
-
-// Following functions are required directly for the DBus functionality.
-
-ImageLoader::ImageLoader(DBus::Connection* conn)
-    : DBus::ObjectAdaptor(*conn, kImageLoaderPath) {}
-
-bool ImageLoader::RegisterComponent(
+bool ImageLoaderImpl::RegisterComponent(
     const std::string& name, const std::string& version,
-    const std::string& component_folder_abs_path, ::DBus::Error& err) {
-  base::FilePath components_dir(kComponentsPath);
+    const std::string& component_folder_abs_path) {
+  base::FilePath components_dir(config_.storage_dir);
   if (!base::PathExists(components_dir)) {
     if (mkdir(components_dir.value().c_str(), kComponentDirPerms) != 0) {
       PLOG(ERROR) << "Could not create the ImageLoader components directory.";
@@ -458,7 +350,7 @@
     }
   }
 
-  if (!AssertComponentDirPerms()) return false;
+  if (!AssertComponentDirPerms(config_.storage_dir)) return false;
 
   base::FilePath component_root = components_dir.Append(name);
 
@@ -518,9 +410,9 @@
   return true;
 }
 
-std::string ImageLoader::GetComponentVersion(const std::string& name,
-                                             ::DBus::Error& err) {
-  base::FilePath component_path = base::FilePath(kComponentsPath).Append(name);
+std::string ImageLoaderImpl::GetComponentVersion(const std::string& name) {
+  base::FilePath component_path =
+      base::FilePath(config_.storage_dir).Append(name);
   if (!base::PathExists(component_path)) {
     LOG(ERROR) << "Component does not exist.";
     return kBadResult;
@@ -533,10 +425,11 @@
     return kBadResult;
   }
 
+  base::FilePath versioned_path = component_path.Append(current_version_hint);
   // The version can be security sensitive (i.e. which flash player does Chrome
   // load), so check the signed manfiest as the final answer.
   std::string manifest_contents;
-  if (!base::ReadFileToStringWithMaxSize(component_path.Append(kManifestName),
+  if (!base::ReadFileToStringWithMaxSize(versioned_path.Append(kManifestName),
                                          &manifest_contents,
                                          kMaximumFilesize)) {
     return kBadResult;
@@ -545,7 +438,7 @@
   // Read in the manifest signature.
   std::string manifest_sig;
   base::FilePath manifest_sig_path =
-      component_path.Append(kManifestSignatureName);
+      versioned_path.Append(kManifestSignatureName);
   if (!base::ReadFileToStringWithMaxSize(manifest_sig_path, &manifest_sig,
                                          kMaximumFilesize)) {
     return kBadResult;
@@ -561,32 +454,14 @@
                                                   : kBadResult;
 }
 
-std::string ImageLoader::LoadComponent(const std::string& name,
-                                       ::DBus::Error& err) {
-  if (reg.find(name) != reg.end()) {
-    if (mounts.find(name) != mounts.end()) {
-      LOG(ERROR) << "Already mounted at " << mounts[name].first.value() << ".";
-      return kBadResult;
-    }
-    std::string mount_point = LoadComponentUtil(name);
-    if (mount_point == kBadResult) {
-      LOG(ERROR) << "Unable to mount : " << mount_point;
-      return kBadResult;
-    }
-    LOG(INFO) << "Mounted successfully at " << mount_point << ".";
-    return mount_point;
+std::string ImageLoaderImpl::LoadComponent(const std::string& name) {
+  std::string mount_point = LoadComponentUtil(name);
+  if (mount_point == kBadResult) {
+    LOG(ERROR) << "Unable to mount : " << mount_point;
+    return kBadResult;
   }
-  LOG(ERROR) << "Entry not found : " << name;
-  return kBadResult;
-}
-
-bool ImageLoader::UnloadComponent(const std::string& name, ::DBus::Error& err) {
-  if (UnloadComponentUtil(name)) {
-    LOG(INFO) << "Unmount " << name << " successful.";
-    return true;
-  }
-  LOG(ERROR) << "Unmount " << name << " unsucessful.";
-  return false;
+  LOG(INFO) << "Mounted successfully at " << mount_point << ".";
+  return mount_point;
 }
 
 }  // namespace imageloader
diff --git a/imageloader_impl.h b/imageloader_impl.h
new file mode 100644
index 0000000..60640ac
--- /dev/null
+++ b/imageloader_impl.h
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef IMAGELOADER_IMAGELOADER_UTILITY_H_
+#define IMAGELOADER_IMAGELOADER_UTILITY_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/gtest_prod_util.h>
+#include <base/macros.h>
+
+namespace imageloader {
+
+struct ImageLoaderConfig {
+  ImageLoaderConfig(const std::vector<uint8_t> key, const char* path)
+      : key(key), storage_dir(path) {}
+  std::vector<uint8_t> key;
+  base::FilePath storage_dir;
+};
+
+class ImageLoaderImpl {
+ public:
+  // Instantiate an object with a configuration object.
+  explicit ImageLoaderImpl(const ImageLoaderConfig& config) : config_(config) {}
+
+  // Register a component.
+  bool RegisterComponent(const std::string& name, const std::string& version,
+                         const std::string& component_folder_abs_path);
+
+  // Get component version given component name.
+  std::string GetComponentVersion(const std::string& name);
+
+  // Load the specified component.
+  std::string LoadComponent(const std::string& name);
+
+ private:
+  // This is a parsed version of the imageloader.json manifest.
+  struct Manifest {
+    int manifest_version;
+    std::vector<uint8_t> image_sha256;
+    std::vector<uint8_t> params_sha256;
+    std::string version;
+  };
+
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ECVerify);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ManifestFingerPrint);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyValidComponent);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyComponentWithBadManifest);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyValidImage);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyInvalidImage);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, CopyInvalidHash);
+  FRIEND_TEST_ALL_PREFIXES(ImageLoaderTest, ParseManifest);
+
+  // Do the work to verify and mount components.
+  std::string LoadComponentUtil(const std::string& name);
+
+  // Verify the data with the RSA (PKCS #1 v1.5) signature.
+  bool ECVerify(const base::StringPiece data, const base::StringPiece sig);
+
+  // Copy the component directory from a user controlled location to an
+  // imageloader controlled location. Do not copy unless it verifies.
+  bool CopyComponentDirectory(const base::FilePath& component_path,
+                              const base::FilePath& destination_folder,
+                              const std::string& version);
+
+  // Check the string contents to see if it matches the format of a
+  // manifest.fingerprint file.
+  bool IsValidFingerprintFile(const std::string& contents);
+
+  // Verify the imageloader.json manifest file and parse the file information
+  // out of it.
+  bool VerifyAndParseManifest(const std::string& manifest_str,
+                              const std::string& signature, Manifest* manifest);
+
+  // Copies files over and checks their hash in the process. The copy fails if
+  // the hashes do not match.
+  bool CopyAndHashFile(const base::FilePath& src_path,
+                       const base::FilePath& dest_path,
+                       const std::vector<uint8_t>& known_hash);
+
+  // Check if the client created a manifest.fingerprint, and preserve it.
+  bool CopyFingerprintFile(const base::FilePath& src,
+                           const base::FilePath& dest);
+
+  // The configuration traits.
+  ImageLoaderConfig config_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageLoaderImpl);
+};
+
+}  // namespace imageloader
+
+#endif  // IMAGELOADER_IMAGELOADER_UTILITY_H_
diff --git a/imageloader_main.cc b/imageloader_main.cc
index de9cf57..cd863fa 100644
--- a/imageloader_main.cc
+++ b/imageloader_main.cc
@@ -9,6 +9,20 @@
 #include "imageloader.h"
 #include "imageloader_common.h"
 
+// TODO(kerrnel): Switch to the prod keys before shipping this feature.
+const uint8_t kDevPublicKey[] = {
+    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+    0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+    0x42, 0x00, 0x04, 0x7a, 0xaa, 0x2b, 0xf9, 0x3d, 0x7a, 0xbe, 0x35, 0x9a,
+    0xfc, 0x9f, 0x39, 0x2d, 0x2d, 0x37, 0x07, 0xd4, 0x19, 0x67, 0x67, 0x30,
+    0xbb, 0x5c, 0x74, 0x22, 0xd5, 0x02, 0x07, 0xaf, 0x6b, 0x12, 0x9d, 0x12,
+    0xf0, 0x34, 0xfd, 0x1a, 0x7f, 0x02, 0xd8, 0x46, 0x2b, 0x25, 0xca, 0xa0,
+    0x6e, 0x2b, 0x54, 0x41, 0xee, 0x92, 0xa2, 0x0f, 0xa2, 0x2a, 0xc0, 0x30,
+    0xa6, 0x8c, 0xd1, 0x16, 0x0a, 0x48, 0xca};
+
+// The path where the components are stored on the device.
+const char kComponentsPath[] = "/mnt/stateful_partition/encrypted/imageloader";
+
 int main(int argc, char** argv) {
   signal(SIGTERM, imageloader::OnQuit);
   signal(SIGINT, imageloader::OnQuit);
@@ -23,7 +37,11 @@
   DBus::default_dispatcher = &dispatcher;
   DBus::Connection conn = DBus::Connection::SystemBus();
   conn.request_name(imageloader::kImageLoaderName);
-  imageloader::ImageLoader helper(&conn);
+
+  std::vector<uint8_t> key(std::begin(kDevPublicKey), std::end(kDevPublicKey));
+
+  imageloader::ImageLoaderConfig config(key, kComponentsPath);
+  imageloader::ImageLoader helper(&conn, config);
 
   if (FLAGS_o) {
     dispatcher.dispatch_pending();
diff --git a/imageloader_unittest.cc b/imageloader_unittest.cc
index 7814561..2e00544 100644
--- a/imageloader_unittest.cc
+++ b/imageloader_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "imageloader_impl.h"
+
 #include <dirent.h>
 #include <stdlib.h>
 
@@ -11,7 +13,6 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "imageloader.h"
 
 #include <base/files/file_enumerator.h>
 #include <base/files/file_path.h>
@@ -28,6 +29,17 @@
 
 namespace {
 
+// Test data always uses the dev key.
+const uint8_t kDevPublicKey[] = {
+    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+    0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+    0x42, 0x00, 0x04, 0x7a, 0xaa, 0x2b, 0xf9, 0x3d, 0x7a, 0xbe, 0x35, 0x9a,
+    0xfc, 0x9f, 0x39, 0x2d, 0x2d, 0x37, 0x07, 0xd4, 0x19, 0x67, 0x67, 0x30,
+    0xbb, 0x5c, 0x74, 0x22, 0xd5, 0x02, 0x07, 0xaf, 0x6b, 0x12, 0x9d, 0x12,
+    0xf0, 0x34, 0xfd, 0x1a, 0x7f, 0x02, 0xd8, 0x46, 0x2b, 0x25, 0xca, 0xa0,
+    0x6e, 0x2b, 0x54, 0x41, 0xee, 0x92, 0xa2, 0x0f, 0xa2, 0x2a, 0xc0, 0x30,
+    0xa6, 0x8c, 0xd1, 0x16, 0x0a, 0x48, 0xca};
+
 const char kImageLoaderJSON[] =
     "{\"image-sha256-hash\":"
     "\"71D11CA4E2B4A3F5E71D789F0E64116F49BB13DE6A591505CA6404985E13F6EF\","
@@ -44,19 +56,36 @@
     0xe8, 0xed, 0x6a, 0x45, 0x38, 0x53, 0x54, 0xd2, 0xb1, 0x97};
 
 const char kImageLoaderBadSig[] = {
-  0x30, 0x44, 0x02, 0x20, 0x0a, 0x75, 0x49, 0xaf, 0x01, 0x3b, 0x48, 0x51,
-  0x45, 0x74, 0x8b, 0x41, 0x64, 0x21, 0x83, 0xce, 0xf1, 0x78, 0x1d, 0xd0,
-  0xa8, 0xd6, 0xae, 0x84, 0xf3, 0xc1, 0x3c, 0x3a, 0xee, 0xb4, 0x35, 0xb7,
-  0x02, 0x20, 0x34, 0xeb, 0xdc, 0x68, 0x2d, 0x8b, 0x4f, 0x64, 0x94, 0x64,
-  0xa3, 0xd5, 0xde, 0xab, 0xf9, 0xa0, 0xbd, 0xcc, 0xc1, 0x2f, 0x78, 0xd4,
-  0xe8, 0xed, 0x6a, 0x45, 0x38, 0x53, 0x54, 0xd2, 0xb1, 0x97
-};
+    0x30, 0x44, 0x02, 0x20, 0x0a, 0x75, 0x49, 0xaf, 0x01, 0x3b, 0x48, 0x51,
+    0x45, 0x74, 0x8b, 0x41, 0x64, 0x21, 0x83, 0xce, 0xf1, 0x78, 0x1d, 0xd0,
+    0xa8, 0xd6, 0xae, 0x84, 0xf3, 0xc1, 0x3c, 0x3a, 0xee, 0xb4, 0x35, 0xb7,
+    0x02, 0x20, 0x34, 0xeb, 0xdc, 0x68, 0x2d, 0x8b, 0x4f, 0x64, 0x94, 0x64,
+    0xa3, 0xd5, 0xde, 0xab, 0xf9, 0xa0, 0xbd, 0xcc, 0xc1, 0x2f, 0x78, 0xd4,
+    0xe8, 0xed, 0x6a, 0x45, 0x38, 0x53, 0x54, 0xd2, 0xb1, 0x97};
+
+// This is the name of the component used in the test data.
+const char kTestComponentName[] = "PepperFlashPlayer";
+// This is the version of flash player used in the test data.
+const char kTestDataVersion[] = "22.0.0.158";
+// This is the version of the updated flash player in the test data.
+const char kTestUpdatedVersion[] = "22.0.0.256";
 
 }  // namespace {}
 
 class ImageLoaderTest : public testing::Test {
  public:
+  ImageLoaderConfig GetConfig(const char* path) {
+    std::vector<uint8_t> key(std::begin(kDevPublicKey),
+                             std::end(kDevPublicKey));
+    ImageLoaderConfig config(key, path);
+    return config;
+  }
+
   base::FilePath GetComponentPath() {
+    return GetComponentPath(kTestDataVersion);
+  }
+
+  base::FilePath GetComponentPath(const std::string& version) {
     const char* src_dir = getenv("CROS_WORKON_SRCROOT");
     CHECK(src_dir != nullptr);
     base::FilePath component_path(src_dir);
@@ -65,7 +94,7 @@
             .Append("platform")
             .Append("imageloader")
             .Append("test")
-            .Append("22.0.0.158_chromeos_intel64_PepperFlashPlayer");
+            .Append(version + "_chromeos_intel64_PepperFlashPlayer");
     return component_path;
   }
 
@@ -78,28 +107,97 @@
   }
 };
 
+// Test the RegisterComponent public interface.
+TEST_F(ImageLoaderTest, RegisterComponentAndGetVersion) {
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  const base::FilePath& temp_dir = scoped_temp_dir.path();
+  // Delete the directory so that the ImageLoader can recreate it with the
+  // correct permissions.
+  base::DeleteFile(temp_dir, /*recursive=*/true);
+
+  ImageLoaderImpl loader(GetConfig(temp_dir.value().c_str()));
+  ASSERT_TRUE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
+                                       GetComponentPath().value()));
+
+  base::FilePath comp_dir = temp_dir.Append(kTestComponentName);
+  ASSERT_TRUE(base::DirectoryExists(comp_dir));
+
+  base::FilePath hint_file = comp_dir.Append(kTestComponentName);
+  ASSERT_TRUE(base::PathExists(hint_file));
+
+  std::string hint_file_contents;
+  ASSERT_TRUE(
+      base::ReadFileToStringWithMaxSize(hint_file, &hint_file_contents, 4096));
+  EXPECT_EQ(kTestDataVersion, hint_file_contents);
+
+  base::FilePath version_dir = comp_dir.Append(kTestDataVersion);
+  ASSERT_TRUE(base::DirectoryExists(version_dir));
+
+  std::list<std::string> files;
+  GetFilesInDir(version_dir, &files);
+  EXPECT_THAT(files, testing::UnorderedElementsAre(
+                         "imageloader.json", "imageloader.sig.1", "params",
+                         "image.squash", "manifest.fingerprint"));
+
+  // Reject a component if the version already exists.
+  EXPECT_FALSE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
+                                       GetComponentPath().value()));
+
+  EXPECT_EQ(kTestDataVersion, loader.GetComponentVersion(kTestComponentName));
+
+  // Now copy a new version into place.
+  EXPECT_TRUE(loader.RegisterComponent(kTestComponentName, kTestUpdatedVersion,
+                                       GetComponentPath(kTestUpdatedVersion).value()));
+
+  std::string hint_file_contents2;
+  ASSERT_TRUE(
+      base::ReadFileToStringWithMaxSize(hint_file, &hint_file_contents2, 4096));
+  EXPECT_EQ(kTestUpdatedVersion, hint_file_contents2);
+
+  base::FilePath version_dir2 = comp_dir.Append(kTestUpdatedVersion);
+  ASSERT_TRUE(base::DirectoryExists(version_dir2));
+
+  std::list<std::string> files2;
+  GetFilesInDir(version_dir2, &files2);
+  EXPECT_THAT(files2, testing::UnorderedElementsAre("imageloader.json",
+                                                    "imageloader.sig.1",
+                                                    "params", "image.squash"));
+
+  EXPECT_EQ(kTestUpdatedVersion, loader.GetComponentVersion(kTestComponentName));
+
+  // Reject rollback to an older version.
+  EXPECT_FALSE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
+                                       GetComponentPath().value()));
+
+  EXPECT_EQ(kTestUpdatedVersion, loader.GetComponentVersion(kTestComponentName));
+}
+
 TEST_F(ImageLoaderTest, ECVerify) {
-  EXPECT_TRUE(ImageLoader::ECVerify(
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
+  EXPECT_TRUE(loader.ECVerify(
       base::StringPiece(kImageLoaderJSON),
       base::StringPiece(kImageLoaderSig, sizeof(kImageLoaderSig))));
 
-  EXPECT_FALSE(ImageLoader::ECVerify(
+  EXPECT_FALSE(loader.ECVerify(
       base::StringPiece(kImageLoaderJSON),
       base::StringPiece(kImageLoaderBadSig, sizeof(kImageLoaderBadSig))));
 }
 
 TEST_F(ImageLoaderTest, ManifestFingerPrint) {
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
   const std::string valid_manifest =
       "1.3464353b1ed78574e05f3ffe84b52582572b2fe7202f3824a3761e54ace8bb1";
-  EXPECT_TRUE(ImageLoader::IsValidFingerprintFile(valid_manifest));
+  EXPECT_TRUE(loader.IsValidFingerprintFile(valid_manifest));
 
   const std::string invalid_unicode_manifest = "Ё Ђ Ѓ Є Ѕ І Ї Ј Љ ";
-  EXPECT_FALSE(ImageLoader::IsValidFingerprintFile(invalid_unicode_manifest));
+  EXPECT_FALSE(loader.IsValidFingerprintFile(invalid_unicode_manifest));
 
-  EXPECT_FALSE(ImageLoader::IsValidFingerprintFile("\x49\x34\x19-43.*+abc"));
+  EXPECT_FALSE(loader.IsValidFingerprintFile("\x49\x34\x19-43.*+abc"));
 }
 
 TEST_F(ImageLoaderTest, CopyValidComponent) {
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
   const int image_size = 4096 * 4;
   base::ScopedTempDir scoped_temp_dir;
   ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
@@ -107,8 +205,8 @@
 
   std::vector<char> image(image_size, 0xBB);
   base::FilePath component_dest = temp_dir.Append("copied-component");
-  ASSERT_TRUE(ImageLoader::CopyComponentDirectory(
-      GetComponentPath(), component_dest, "22.0.0.158"));
+  ASSERT_TRUE(loader.CopyComponentDirectory(GetComponentPath(), component_dest,
+                                            kTestDataVersion));
 
   // Check that all the files are present, except for the manifest.json which
   // should be discarded.
@@ -141,8 +239,9 @@
   const char data[] = "c";
   ASSERT_TRUE(base::AppendToFile(manifest, data, sizeof(data)));
 
-  EXPECT_FALSE(ImageLoader::CopyComponentDirectory(
-      bad_component_dir, temp_dir.Append("copied-component"), "22.0.0.158"));
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
+  EXPECT_FALSE(loader.CopyComponentDirectory(
+      bad_component_dir, temp_dir.Append("copied-component"), kTestDataVersion));
 }
 
 TEST_F(ImageLoaderTest, CopyValidImage) {
@@ -165,8 +264,9 @@
   sha256->Update(image.data(), image.size());
   sha256->Finish(hash.data(), hash.size());
 
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
   base::FilePath image_dest = temp_dir.Append("image.copied");
-  ASSERT_TRUE(ImageLoader::CopyAndHashFile(image_path, image_dest, hash));
+  ASSERT_TRUE(loader.CopyAndHashFile(image_path, image_dest, hash));
 
   // Check if the image file actually exists and has the correct contents.
   std::string resulting_image;
@@ -194,29 +294,28 @@
   std::vector<char> file(image_size, 0xAA);
   ASSERT_EQ(image_size, base::WriteFile(image_src, file.data(), image_size));
 
-  EXPECT_FALSE(ImageLoader::CopyAndHashFile(image_src, image_dest, hash));
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
+  EXPECT_FALSE(loader.CopyAndHashFile(image_src, image_dest, hash));
 }
 
 TEST_F(ImageLoaderTest, ParseManifest) {
-  ImageLoader::Manifest manifest;
-  ASSERT_TRUE(ImageLoader::VerifyAndParseManifest(kImageLoaderJSON,
-                                                  kImageLoaderSig,
-                                                  &manifest));
+  ImageLoaderImpl loader(GetConfig("/nonexistant"));
+  ImageLoaderImpl::Manifest manifest;
+  ASSERT_TRUE(loader.VerifyAndParseManifest(kImageLoaderJSON, kImageLoaderSig,
+                                            &manifest));
   EXPECT_EQ(1, manifest.manifest_version);
-  EXPECT_EQ("22.0.0.158", manifest.version);
+  EXPECT_EQ(kTestDataVersion, manifest.version);
   EXPECT_EQ(32, manifest.image_sha256.size());
   EXPECT_EQ(32, manifest.params_sha256.size());
 
   std::string bad_manifest = "{\"foo\":\"128.0.0.9\"}";
-  ImageLoader::Manifest manifest2;
-  EXPECT_FALSE(ImageLoader::VerifyAndParseManifest(bad_manifest,
-                                                   kImageLoaderSig,
-                                                   &manifest2));
+  ImageLoaderImpl::Manifest manifest2;
+  EXPECT_FALSE(
+      loader.VerifyAndParseManifest(bad_manifest, kImageLoaderSig, &manifest2));
 
-  ImageLoader::Manifest manifest3;
-  EXPECT_FALSE(ImageLoader::VerifyAndParseManifest(kImageLoaderJSON,
-                                                   kImageLoaderBadSig,
-                                                   &manifest3));
+  ImageLoaderImpl::Manifest manifest3;
+  EXPECT_FALSE(loader.VerifyAndParseManifest(kImageLoaderJSON,
+                                             kImageLoaderBadSig, &manifest3));
 }
 
-}
+}   // namespace imageloader
diff --git a/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/image.squash b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/image.squash
new file mode 100644
index 0000000..8f18ea7
--- /dev/null
+++ b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/image.squash
Binary files differ
diff --git a/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.json b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.json
new file mode 100644
index 0000000..a143fb7
--- /dev/null
+++ b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.json
@@ -0,0 +1 @@
+{"image-sha256-hash":"2470C8EBE59C532535FB9F02BC900A02434B356559FBF6373B9428BF241C11C5","version":"22.0.0.256","params-sha256-hash":"D42F5F340A13ED293EBD7E7C753BCA4EA4D471DB78D97906EE4587D04695083B","manifest-version":1}
\ No newline at end of file
diff --git a/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.sig.1 b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.sig.1
new file mode 100644
index 0000000..437c754
--- /dev/null
+++ b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/imageloader.sig.1
@@ -0,0 +1 @@
+0D Emy¨>ÕInŠÝÍð¨ö)|.•gûJ9í®·ÔýKu DºÑ‚ƒo2YŸ¥¤ïÄæô30ËÙ¨é‚Ù¦ï:Uuâ
\ No newline at end of file
diff --git a/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/manifest.json b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/manifest.json
new file mode 100644
index 0000000..4677d41
--- /dev/null
+++ b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/manifest.json
@@ -0,0 +1,5 @@
+{
+  "manifest_version": 2,
+  "name": "PepperFlashPlayer",
+  "version": "22.0.0.256"
+}
\ No newline at end of file
diff --git a/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/params b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/params
new file mode 100644
index 0000000..eb22597
--- /dev/null
+++ b/test/22.0.0.256_chromeos_intel64_PepperFlashPlayer/params
@@ -0,0 +1 @@
+0 24 verity payload=ROOT_DEV hashtree=HASH_DEV hashstart=24 alg=sha256 root_hexdigest=fcd810fe1c1849f636334b18ca7bfd931d895e7a0d4661da32751e61507c772d salt=9893b9f2b4ae18dde95c417a0be00530c6fe036205ed6fe98840c7aa02430ece