blob: 17df82724c77e6d9b53fcfd886cc12d36b0ec3d5 [file] [log] [blame]
// Copyright 2018 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.
#include "oobe_config/load_oobe_config_usb.h"
#include <sys/mount.h>
#include <map>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include "oobe_config/usb_utils.h"
using base::FilePath;
using base::ScopedTempDir;
using std::map;
using std::string;
using std::unique_ptr;
using std::vector;
namespace oobe_config {
unique_ptr<LoadOobeConfigUsb> LoadOobeConfigUsb::CreateInstance() {
return std::make_unique<LoadOobeConfigUsb>(FilePath(kStatefulDir),
FilePath(kDevDiskById));
}
LoadOobeConfigUsb::LoadOobeConfigUsb(const FilePath& stateful_dir,
const FilePath& device_ids_dir)
: stateful_(stateful_dir),
device_ids_dir_(device_ids_dir),
public_key_(nullptr),
config_is_verified_(false) {
unencrypted_oobe_config_dir_ = stateful_.Append(kUnencryptedOobeConfigDir);
pub_key_file_ = unencrypted_oobe_config_dir_.Append(kKeyFile);
config_signature_file_ =
unencrypted_oobe_config_dir_.Append(kConfigFile).AddExtension("sig");
enrollment_domain_signature_file_ =
unencrypted_oobe_config_dir_.Append(kDomainFile).AddExtension("sig");
usb_device_path_signature_file_ =
unencrypted_oobe_config_dir_.Append(kUsbDevicePathSigFile);
}
bool LoadOobeConfigUsb::ReadFiles() {
if (!base::PathExists(stateful_)) {
LOG(ERROR) << "Stateful partition's path " << stateful_.value()
<< " does not exist.";
return false;
}
if (!base::PathExists(unencrypted_oobe_config_dir_)) {
LOG(WARNING) << "oobe_config directory on stateful partition "
<< unencrypted_oobe_config_dir_.value()
<< " does not exist. This is not an error if the system is not"
<< " configured for auto oobe.";
return false;
}
if (!base::PathExists(pub_key_file_)) {
LOG(WARNING) << "Public key file " << pub_key_file_.value()
<< " does not exist. ";
return false;
}
if (!ReadPublicKey(pub_key_file_, &public_key_)) {
return false;
}
map<FilePath, string*> signature_files = {
{config_signature_file_, &config_signature_},
{enrollment_domain_signature_file_, &enrollment_domain_signature_},
{usb_device_path_signature_file_, &usb_device_path_signature_}};
for (auto& file : signature_files) {
if (!base::PathExists(file.first)) {
LOG(WARNING) << "File " << file.first.value() << " does not exist. ";
return false;
}
if (!base::ReadFileToString(file.first, file.second)) {
PLOG(ERROR) << "Failed to read file: " << file.first.value();
return false;
}
}
return true;
}
bool LoadOobeConfigUsb::VerifyPublicKey() {
// TODO(ahassani): calculate the SHA256 of the public key and return false if
// doesn't match the one in TPM.
// /stateful/unencrypted/oobe_auto_config/validation_key.pub is the key used
// for verifying signatures. The binary SHA256 digest of this file should
// match the 32 bytes in the TPM at index 0x100c, otherwise abort.
return false;
}
bool LoadOobeConfigUsb::LocateUsbDevice(FilePath* device_id) {
// /stateful/unencrypted/oobe_auto_config/usb_device_path.sig is the signature
// of a /dev/disk/by-id path for the root USB device (e.g. /dev/sda). So to
// obtain which USB we were on pre-reboot:
// for dev in /dev/disk/by-id/ *:
// if dev verifies with usb_device_path.sig with validation_key.pub:
// USB block device is at readlink(dev)
base::FileEnumerator iter(device_ids_dir_, false,
base::FileEnumerator::FILES);
for (auto link = iter.Next(); !link.empty(); link = iter.Next()) {
if (VerifySignature(link.value(), usb_device_path_signature_,
public_key_) &&
base::NormalizeFilePath(link, device_id)) {
LOG(INFO) << "Found USB device " << device_id->value();
return true;
}
}
LOG(ERROR) << "Did not find the USB device. Probably it was taken out?";
return false;
}
bool LoadOobeConfigUsb::VerifyEnrollmentDomainInConfig(
const string& config, const string& enrollment_domain) {
// TODO(ahassani): Implement this.
return true;
}
bool LoadOobeConfigUsb::MountUsbDevice(const FilePath& device_path,
const FilePath& mount_point) {
LOG(INFO) << "Mounting " << device_path.value() << " on "
<< mount_point.value();
auto kMountFlags = MS_RDONLY | MS_NOEXEC | MS_NOSUID | MS_NODEV;
if (mount(device_path.value().c_str(), mount_point.value().c_str(), "ext4",
kMountFlags, nullptr) != 0) {
PLOG(ERROR) << "Failed to mount " << device_path.value() << "on "
<< mount_point.value();
return false;
}
return true;
}
bool LoadOobeConfigUsb::UnmountUsbDevice(const FilePath& mount_point) {
LOG(INFO) << "Unmounting " << mount_point.value();
if (umount(mount_point.value().c_str()) != 0) {
PLOG(WARNING) << "Failed to unmount " << mount_point.value();
return false;
}
return true;
}
bool LoadOobeConfigUsb::GetOobeConfigJson(string* config,
string* enrollment_domain) {
// We have already verified the config, just return it.
CHECK(config != nullptr);
CHECK(enrollment_domain != nullptr);
if (config_is_verified_) {
*config = config_;
*enrollment_domain = enrollment_domain_;
return true;
}
if (!ReadFiles()) {
return false;
}
if (!VerifyPublicKey()) {
return false;
}
// By now we have all the files necessary on the stateful partition. Now we
// have to look into the USB drives.
FilePath device_path;
if (!LocateUsbDevice(&device_path)) {
return false;
}
ScopedTempDir usb_mount_path;
if (!usb_mount_path.CreateUniqueTempDir() ||
!MountUsbDevice(device_path, usb_mount_path.GetPath())) {
return false;
}
// /stateful/unencrypted/oobe_auto_config/config.json.sig is the signature of
// the config.json file on the USB stateful.
FilePath unencrypted_oobe_config_dir_on_usb =
usb_mount_path.GetPath().Append(kUnencryptedOobeConfigDir);
FilePath config_file_on_usb =
unencrypted_oobe_config_dir_on_usb.Append(kConfigFile);
if (!base::ReadFileToString(config_file_on_usb, &config_)) {
LOG(ERROR) << "Failed to read oobe config file: "
<< config_file_on_usb.value();
return false;
}
if (!VerifySignature(config_, config_signature_, public_key_)) {
return false;
}
// /stateful/unencrypted/oobe_auto_config/enrollment_domain.sig is the
// signature of the enrollment_domain file on the USB stateful.
FilePath enrollment_domain_file_on_usb =
unencrypted_oobe_config_dir_on_usb.Append(kDomainFile);
if (!base::ReadFileToString(enrollment_domain_file_on_usb,
&enrollment_domain_)) {
LOG(ERROR) << "Failed to read enrollment domain file: "
<< enrollment_domain_file_on_usb.value();
return false;
}
if (!VerifySignature(enrollment_domain_, enrollment_domain_signature_,
public_key_)) {
return false;
}
// The config.json has only an enrollment token, not a plain text domain, so
// at some point before enrolling we have to verify that the token in
// config.json matches the domain name in enrollment_domain, because that's
// what we showed to the user in recovery.
if (!VerifyEnrollmentDomainInConfig(config_, enrollment_domain_)) {
return false;
}
*config = config_;
*enrollment_domain = enrollment_domain_;
config_is_verified_ = true;
// Ignore the failure.
UnmountUsbDevice(usb_mount_path.GetPath());
return true;
}
} // namespace oobe_config