| // 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/check.h> |
| #include <base/check_op.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/bindings/remote.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(), tmp_password)); |
| return password_provider::Password::CreateFromFileDescriptor( |
| read_fd.get(), tmp_password.size()); |
| } |
| |
| } // namespace |
| |
| SmbFsBootstrapImpl::SmbFsBootstrapImpl( |
| mojo::PendingReceiver<mojom::SmbFsBootstrap> receiver, |
| SmbFilesystemFactory smb_filesystem_factory, |
| Delegate* delegate, |
| const base::FilePath& daemon_store_root) |
| : receiver_(this, std::move(receiver)), |
| 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()); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &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, |
| mojo::PendingRemote<mojom::SmbFsDelegate> smbfs_delegate, |
| MountShareCallback callback) { |
| if (!completion_callback_) { |
| LOG(ERROR) << "Mojo bootstrap not active"; |
| std::move(callback).Run(mojom::MountError::kUnknown, |
| mojo::PendingRemote<smbfs::mojom::SmbFs>()); |
| 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, |
| mojo::PendingRemote<smbfs::mojom::SmbFs>()); |
| 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, |
| mojo::PendingRemote<mojom::SmbFsDelegate> 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, |
| mojo::PendingRemote<smbfs::mojom::SmbFs>()); |
| 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, |
| mojo::PendingRemote<smbfs::mojom::SmbFs>()); |
| 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), |
| mojo::PendingRemote<smbfs::mojom::SmbFs>()); |
| 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)); |
| } |
| |
| mojo::PendingRemote<mojom::SmbFs> smbfs; |
| std::move(completion_callback_) |
| .Run(std::move(fs), smbfs.InitWithNewPipeAndPassReceiver(), |
| std::move(smbfs_delegate)); |
| |
| std::move(callback).Run(mojom::MountError::kOk, std::move(smbfs)); |
| } |
| |
| void SmbFsBootstrapImpl::OnMojoConnectionError() { |
| if (completion_callback_) { |
| std::move(completion_callback_) |
| .Run(nullptr, |
| mojo::PendingRemote<smbfs::mojom::SmbFs>() |
| .InitWithNewPipeAndPassReceiver(), |
| mojo::PendingRemote<smbfs::mojom::SmbFsDelegate>()); |
| } |
| } |
| |
| base::FilePath SmbFsBootstrapImpl::GetUserDaemonStoreDirectory( |
| const std::string& username_hash) const { |
| CHECK(!username_hash.empty()); |
| return daemon_store_root_.Append(username_hash); |
| } |
| |
| } // namespace smbfs |