blob: 6b8835c09aca83647d6152f3dcbaccbd13c7484e [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 <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_common.h"
#include "oobe_config/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) {
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);
config_is_verified_ = false;
public_key_ = nullptr;
}
bool LoadOobeConfigUsb::ReadFiles(bool ignore_errors) {
if (!base::PathExists(stateful_) && !ignore_errors) {
LOG(ERROR) << "Stateful partition's path " << stateful_.value()
<< " does not exist.";
return false;
}
if (!base::PathExists(unencrypted_oobe_config_dir_) && !ignore_errors) {
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_) && !ignore_errors) {
LOG(WARNING) << "Public key file " << pub_key_file_.value()
<< " does not exist. ";
return false;
}
if (!ReadPublicKey() && !ignore_errors) {
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) && !ignore_errors) {
LOG(WARNING) << "File " << file.first.value() << " does not exist. ";
return false;
}
if (!base::ReadFileToString(file.first, file.second) && !ignore_errors) {
PLOG(ERROR) << "Failed to read file: " << file.first.value();
return false;
}
}
return true;
}
bool LoadOobeConfigUsb::ReadPublicKey() {
base::ScopedFILE pkf(base::OpenFile(pub_key_file_, "r"));
if (!pkf) {
PLOG(ERROR) << "Failed to open the public key file "
<< pub_key_file_.value();
return false;
}
public_key_.reset(PEM_read_PUBKEY(pkf.get(), nullptr, nullptr, nullptr));
if (!public_key_) {
LOG(ERROR) << "Failed to read the PEM public key file "
<< pub_key_file_.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::VerifySignature(const string& message,
const string& signature) {
crypto::ScopedEVP_MD_CTX mdctx(EVP_MD_CTX_create());
if (!mdctx) {
LOG(ERROR) << "Failed to create a EVP_MD context.";
return false;
}
if (EVP_DigestVerifyInit(mdctx.get(), nullptr, EVP_sha256(), nullptr,
public_key_.get()) != 1 ||
EVP_DigestVerifyUpdate(mdctx.get(), message.c_str(), message.length()) !=
1 ||
EVP_DigestVerifyFinal(
mdctx.get(), reinterpret_cast<const unsigned char*>(signature.data()),
signature.size()) != 1) {
LOG(ERROR) << "Failed to verify the signature.";
return false;
}
return true;
}
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_) &&
base::ReadSymbolicLink(link, device_id)) {
// Found the device, did a `readlink -f` and returning the absolute path
// to the device.
*device_id = base::MakeAbsoluteFilePath(*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 false;
}
bool LoadOobeConfigUsb::MountUsbDevice(const FilePath& device_path,
const FilePath& mount_point) {
if (RunCommand({"mount", "-n", "-o", "ro", device_path.value(),
mount_point.value()}) != 0) {
LOG(ERROR) << "Failed to mount " << device_path.value();
return false;
}
return true;
}
bool LoadOobeConfigUsb::UnmountUsbDevice(const FilePath& mount_point) {
if (RunCommand({"umount", mount_point.value()}) != 0) {
LOG(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.
if (config_is_verified_) {
*config = config_;
*enrollment_domain = enrollment_domain_;
return true;
}
if (!ReadFiles(false /* ignore_errors */)) {
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_)) {
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_)) {
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