blob: cffe291864db58759ef12a641f987700322aaca3 [file] [log] [blame]
// Copyright 2020 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 "smbfs/smbfs_bootstrap_impl.h"
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/logging.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/strcat.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/secure_blob.h>
#include <crypto/hmac.h>
#include <libpasswordprovider/password.h>
#include <mojo/public/cpp/system/platform_handle.h>
#include "smbfs/smb_credential.h"
#include "smbfs/smb_filesystem.h"
namespace smbfs {
namespace {
mojom::MountError ConnectErrorToMountError(SmbFilesystem::ConnectError error) {
switch (error) {
case SmbFilesystem::ConnectError::kNotFound:
return mojom::MountError::kNotFound;
case SmbFilesystem::ConnectError::kAccessDenied:
return mojom::MountError::kAccessDenied;
case SmbFilesystem::ConnectError::kSmb1Unsupported:
return mojom::MountError::kInvalidProtocol;
default:
return mojom::MountError::kUnknown;
}
}
base::FilePath MakePasswordFileName(const std::string& share_path,
const std::string& username,
const std::string& workgroup,
const std::vector<uint8_t>& salt) {
// Normally, this could produce overlapping strings. eg. with
// username/workgroup: "abc"/"def" and "a"/"bcdef". However, the salt ensures
// the final filename is unique even if two mounts produce the same
// |raw_name|.
const std::string raw_name = base::StrCat({share_path, username, workgroup});
crypto::HMAC hmac(crypto::HMAC::SHA256);
CHECK(hmac.Init(salt.data(), salt.size()));
const size_t hash_len = hmac.DigestLength();
std::unique_ptr<unsigned char[]> raw_hash =
std::make_unique<unsigned char[]>(hash_len);
CHECK(hmac.Sign(raw_name, raw_hash.get(), hash_len));
return base::FilePath(base::HexEncode(raw_hash.get(), hash_len));
}
brillo::SecureVector ObfuscatePassword(
const password_provider::Password& password,
const std::vector<uint8_t>& salt) {
brillo::SecureVector obfuscated(password.GetRaw(),
password.GetRaw() + password.size());
// Obfuscate the password using the salt.
for (size_t i = 0; i < obfuscated.size(); i++) {
obfuscated[i] ^= salt[i % salt.size()];
}
return obfuscated;
}
bool SavePasswordToFile(const base::FilePath& file_path,
const brillo::SecureVector& obfuscated_password) {
base::File password_file(
file_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!password_file.IsValid()) {
LOG(ERROR) << "Unable to open password file for write with error: "
<< password_file.error_details();
return false;
}
int written = password_file.WriteAtCurrentPos(
reinterpret_cast<const char*>(obfuscated_password.data()),
obfuscated_password.size());
return written == obfuscated_password.size();
}
std::unique_ptr<password_provider::Password> ReadPasswordFromFile(
const base::FilePath& file_path, const std::vector<uint8_t>& salt) {
base::File password_file(file_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!password_file.IsValid()) {
LOG(ERROR) << "Unable to open password file with error: "
<< password_file.error_details();
return nullptr;
}
brillo::SecureVector tmp_password(password_file.GetLength());
int read = password_file.ReadAtCurrentPos(
reinterpret_cast<char*>(tmp_password.data()), password_file.GetLength());
if (read != password_file.GetLength()) {
LOG(ERROR) << "Unexpected password file read length: " << read;
return nullptr;
}
if (!salt.empty()) {
for (size_t i = 0; i < tmp_password.size(); i++) {
tmp_password[i] ^= salt[i % salt.size()];
}
}
int fds[2];
CHECK(base::CreateLocalNonBlockingPipe(fds));
base::ScopedFD read_fd(fds[0]);
base::ScopedFD write_fd(fds[1]);
CHECK(base::WriteFileDescriptor(
write_fd.get(), reinterpret_cast<const char*>(tmp_password.data()),
tmp_password.size()));
return password_provider::Password::CreateFromFileDescriptor(
read_fd.get(), tmp_password.size());
}
} // namespace
SmbFsBootstrapImpl::SmbFsBootstrapImpl(
mojom::SmbFsBootstrapRequest request,
SmbFilesystemFactory smb_filesystem_factory,
Delegate* delegate,
const base::FilePath& daemon_store_root)
: binding_(this, std::move(request)),
smb_filesystem_factory_(smb_filesystem_factory),
delegate_(delegate),
daemon_store_root_(daemon_store_root) {
DCHECK(smb_filesystem_factory_);
DCHECK(delegate_);
DCHECK(!daemon_store_root_.empty());
binding_.set_connection_error_handler(base::Bind(
&SmbFsBootstrapImpl::OnMojoConnectionError, base::Unretained(this)));
}
SmbFsBootstrapImpl::~SmbFsBootstrapImpl() = default;
void SmbFsBootstrapImpl::Start(BootstrapCompleteCallback callback) {
DCHECK(!completion_callback_);
completion_callback_ = std::move(callback);
}
void SmbFsBootstrapImpl::MountShare(mojom::MountOptionsPtr options,
mojom::SmbFsDelegatePtr smbfs_delegate,
MountShareCallback callback) {
if (!completion_callback_) {
LOG(ERROR) << "Mojo bootstrap not active";
std::move(callback).Run(mojom::MountError::kUnknown, nullptr);
return;
}
if (options->share_path.find("smb://") != 0) {
// TODO(amistry): More extensive URL validation.
LOG(ERROR) << "Invalid share path: " << options->share_path;
std::move(callback).Run(mojom::MountError::kInvalidUrl, nullptr);
return;
}
std::unique_ptr<SmbCredential> credential = std::make_unique<SmbCredential>(
options->workgroup, options->username, nullptr);
if (options->kerberos_config) {
delegate_->SetupKerberos(
std::move(options->kerberos_config),
base::BindOnce(&SmbFsBootstrapImpl::OnCredentialsSetup,
base::Unretained(this), std::move(options),
std::move(smbfs_delegate), std::move(callback),
std::move(credential), true /* use_kerberos */));
return;
}
if (options->password) {
credential->password = std::move(options->password.value());
}
OnCredentialsSetup(std::move(options), std::move(smbfs_delegate),
std::move(callback), std::move(credential),
false /* use_kerberos */, true /* setup_success */);
}
void SmbFsBootstrapImpl::OnCredentialsSetup(
mojom::MountOptionsPtr options,
mojom::SmbFsDelegatePtr smbfs_delegate,
MountShareCallback callback,
std::unique_ptr<SmbCredential> credential,
bool use_kerberos,
bool setup_success) {
DCHECK(credential);
if (!setup_success) {
std::move(callback).Run(mojom::MountError::kUnknown, nullptr);
return;
}
base::FilePath pass_file_path;
brillo::SecureVector obfuscated_password;
if (!use_kerberos && options->credential_storage_options &&
!credential->username.empty()) {
CHECK_GE(options->credential_storage_options->salt.size(),
mojom::CredentialStorageOptions::kMinSaltLength);
const base::FilePath pass_file_name = MakePasswordFileName(
options->share_path, credential->username, credential->workgroup,
options->credential_storage_options->salt);
pass_file_path = GetUserDaemonStoreDirectory(
options->credential_storage_options->account_hash)
.Append(pass_file_name);
delegate_->OnPasswordFilePathSet(pass_file_path);
if (credential->password) {
// We obfuscate the password now because |credential| is moved into the
// SmbFilesystem and is unavailable when we know the connection is
// successful.
obfuscated_password = ObfuscatePassword(
*credential->password, options->credential_storage_options->salt);
} else {
credential->password = ReadPasswordFromFile(
pass_file_path, options->credential_storage_options->salt);
}
}
SmbFilesystem::Options smb_options;
smb_options.share_path = options->share_path;
smb_options.credentials = std::move(credential);
smb_options.allow_ntlm = options->allow_ntlm;
auto fs = smb_filesystem_factory_.Run(std::move(smb_options));
// Don't use the resolved address if Kerberos is set up. Kerberos requires the
// full hostname to obtain auth tickets.
if (options->resolved_host && !use_kerberos) {
if (options->resolved_host->address_bytes.size() != 4) {
LOG(ERROR) << "Invalid IP address size: "
<< options->resolved_host->address_bytes.size();
std::move(callback).Run(mojom::MountError::kInvalidOptions, nullptr);
return;
}
fs->SetResolvedAddress(options->resolved_host->address_bytes);
}
if (!options->skip_connect) {
SmbFilesystem::ConnectError error = fs->EnsureConnected();
if (error != SmbFilesystem::ConnectError::kOk) {
LOG(ERROR) << "Unable to connect to SMB share " << options->share_path
<< ": " << error;
std::move(callback).Run(ConnectErrorToMountError(error), nullptr);
return;
}
}
// Now that the share connection was successful, we can save the password.
if (!obfuscated_password.empty()) {
DCHECK(!pass_file_path.empty());
CHECK(SavePasswordToFile(pass_file_path, obfuscated_password));
}
mojom::SmbFsPtr smbfs_ptr;
std::move(completion_callback_)
.Run(std::move(fs), mojo::MakeRequest(&smbfs_ptr),
std::move(smbfs_delegate));
std::move(callback).Run(mojom::MountError::kOk, std::move(smbfs_ptr));
}
void SmbFsBootstrapImpl::OnMojoConnectionError() {
if (completion_callback_) {
std::move(completion_callback_).Run(nullptr, nullptr, nullptr);
}
}
base::FilePath SmbFsBootstrapImpl::GetUserDaemonStoreDirectory(
const std::string& username_hash) const {
CHECK(!username_hash.empty());
return daemon_store_root_.Append(username_hash);
}
} // namespace smbfs