blob: cd96f3c6dc100d9e3913f7745b6db167058a3280 [file] [log] [blame]
// Copyright 2017 The Chromium 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 "chromeos/login/auth/authpolicy_login_helper.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "chromeos/dbus/auth_policy_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/upstart_client.h"
#include "chromeos/dbus/util/tpm_util.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/symmetric_key.h"
namespace chromeos {
namespace {
constexpr char kDCPrefix[] = "DC=";
constexpr char kOUPrefix[] = "OU=";
base::ScopedFD GetDataReadPipe(const std::string& data) {
int pipe_fds[2];
if (!base::CreateLocalNonBlockingPipe(pipe_fds)) {
DLOG(ERROR) << "Failed to create pipe";
return base::ScopedFD();
}
base::ScopedFD pipe_read_end(pipe_fds[0]);
base::ScopedFD pipe_write_end(pipe_fds[1]);
if (!base::WriteFileDescriptor(pipe_write_end.get(), data.c_str(),
data.size())) {
DLOG(ERROR) << "Failed to write to pipe";
return base::ScopedFD();
}
return pipe_read_end;
}
bool ParseDomainAndOU(const std::string& distinguished_name,
authpolicy::JoinDomainRequest* request) {
std::string machine_domain;
std::vector<std::string> split_dn =
base::SplitString(distinguished_name, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
for (const std::string& str : split_dn) {
if (base::StartsWith(str, kOUPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
*request->add_machine_ou() = str.substr(strlen(kOUPrefix));
} else if (base::StartsWith(str, kDCPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
if (!machine_domain.empty())
machine_domain.append(".");
machine_domain.append(str.substr(strlen(kDCPrefix)));
} else {
return false;
}
}
if (!machine_domain.empty())
request->set_machine_domain(machine_domain);
return true;
}
std::string DoDecrypt(const std::string& encrypted_data,
const std::string& password) {
constexpr char error_msg[] = "Failed to decrypt data";
const size_t kSaltSize = 32;
const size_t kSignatureSize = 32;
if (encrypted_data.size() <= kSaltSize + kSignatureSize) {
LOG(ERROR) << error_msg;
return std::string();
}
const std::string salt = encrypted_data.substr(0, kSaltSize);
const std::string signature =
encrypted_data.substr(kSaltSize, kSignatureSize);
const std::string ciphertext =
encrypted_data.substr(kSaltSize + kSignatureSize);
// Derive AES key, AES IV and HMAC key from password.
const size_t kAesKeySize = 32;
const size_t kAesIvSize = 16;
const size_t kHmacKeySize = 32;
const size_t kKeySize = kAesKeySize + kAesIvSize + kHmacKeySize;
std::unique_ptr<crypto::SymmetricKey> key =
crypto::SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
crypto::SymmetricKey::HMAC_SHA1, password, salt, 10000, kKeySize * 8);
if (!key) {
LOG(ERROR) << error_msg;
return std::string();
}
DCHECK(kAesKeySize + kAesIvSize + kHmacKeySize == key->key().size());
const char* key_data_chars = key->key().data();
std::string aes_key(key_data_chars, kAesKeySize);
std::string aes_iv(key_data_chars + kAesKeySize, kAesIvSize);
std::string hmac_key(key_data_chars + kAesKeySize + kAesIvSize, kHmacKeySize);
// Check signature.
crypto::HMAC hmac(crypto::HMAC::SHA256);
if (kSignatureSize != hmac.DigestLength()) {
LOG(ERROR) << error_msg;
return std::string();
}
uint8_t recomputed_signature[kSignatureSize];
if (!hmac.Init(hmac_key) ||
!hmac.Sign(ciphertext, recomputed_signature, kSignatureSize)) {
LOG(ERROR) << error_msg;
return std::string();
}
std::string recomputed_signature_str(
reinterpret_cast<char*>(recomputed_signature), kSignatureSize);
if (signature != recomputed_signature_str) {
LOG(ERROR) << error_msg;
return std::string();
}
// Decrypt.
std::unique_ptr<crypto::SymmetricKey> aes_key_obj(
crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, aes_key));
crypto::Encryptor encryptor;
if (!encryptor.Init(aes_key_obj.get(), crypto::Encryptor::CBC, aes_iv)) {
LOG(ERROR) << error_msg;
return std::string();
}
std::string decrypted_data;
if (!encryptor.Decrypt(ciphertext, &decrypted_data)) {
LOG(ERROR) << error_msg;
return std::string();
}
return decrypted_data;
}
} // namespace
AuthPolicyLoginHelper::AuthPolicyLoginHelper() : weak_factory_(this) {}
// static
void AuthPolicyLoginHelper::TryAuthenticateUser(const std::string& username,
const std::string& object_guid,
const std::string& password) {
authpolicy::AuthenticateUserRequest request;
request.set_user_principal_name(username);
request.set_account_id(object_guid);
chromeos::DBusThreadManager::Get()->GetAuthPolicyClient()->AuthenticateUser(
request, GetDataReadPipe(password).get(), base::DoNothing());
}
// static
void AuthPolicyLoginHelper::Restart() {
chromeos::DBusThreadManager::Get()
->GetUpstartClient()
->RestartAuthPolicyService();
}
// static
void AuthPolicyLoginHelper::DecryptConfiguration(const std::string& blob,
const std::string& password,
OnDecryptedCallback callback) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&DoDecrypt, blob, password), std::move(callback));
}
void AuthPolicyLoginHelper::JoinAdDomain(const std::string& machine_name,
const std::string& distinguished_name,
int encryption_types,
const std::string& username,
const std::string& password,
JoinCallback callback) {
DCHECK(!tpm_util::IsActiveDirectoryLocked());
DCHECK(!weak_factory_.HasWeakPtrs()) << "Another operation is in progress";
authpolicy::JoinDomainRequest request;
if (!ParseDomainAndOU(distinguished_name, &request)) {
DLOG(ERROR) << "Failed to parse computer distinguished name";
std::move(callback).Run(authpolicy::ERROR_INVALID_OU, std::string());
return;
}
if (!machine_name.empty())
request.set_machine_name(machine_name);
DCHECK(authpolicy::KerberosEncryptionTypes_IsValid(encryption_types));
request.set_kerberos_encryption_types(
static_cast<authpolicy::KerberosEncryptionTypes>(encryption_types));
if (!username.empty())
request.set_user_principal_name(username);
DCHECK(!dm_token_.empty());
request.set_dm_token(dm_token_);
chromeos::DBusThreadManager::Get()->GetAuthPolicyClient()->JoinAdDomain(
request, GetDataReadPipe(password).get(),
base::BindOnce(&AuthPolicyLoginHelper::OnJoinCallback,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AuthPolicyLoginHelper::AuthenticateUser(const std::string& username,
const std::string& object_guid,
const std::string& password,
AuthCallback callback) {
DCHECK(!weak_factory_.HasWeakPtrs()) << "Another operation is in progress";
authpolicy::AuthenticateUserRequest request;
request.set_user_principal_name(username);
request.set_account_id(object_guid);
chromeos::DBusThreadManager::Get()->GetAuthPolicyClient()->AuthenticateUser(
request, GetDataReadPipe(password).get(),
base::BindOnce(&AuthPolicyLoginHelper::OnAuthCallback,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AuthPolicyLoginHelper::CancelRequestsAndRestart() {
weak_factory_.InvalidateWeakPtrs();
dm_token_.clear();
AuthPolicyLoginHelper::Restart();
}
void AuthPolicyLoginHelper::OnJoinCallback(JoinCallback callback,
authpolicy::ErrorType error,
const std::string& machine_domain) {
DCHECK(!tpm_util::IsActiveDirectoryLocked());
if (error != authpolicy::ERROR_NONE) {
std::move(callback).Run(error, machine_domain);
return;
}
chromeos::DBusThreadManager::Get()
->GetAuthPolicyClient()
->RefreshDevicePolicy(base::BindOnce(
&AuthPolicyLoginHelper::OnFirstPolicyRefreshCallback,
weak_factory_.GetWeakPtr(), std::move(callback), machine_domain));
}
void AuthPolicyLoginHelper::OnFirstPolicyRefreshCallback(
JoinCallback callback,
const std::string& machine_domain,
authpolicy::ErrorType error) {
DCHECK(!tpm_util::IsActiveDirectoryLocked());
// First policy refresh happens before device is locked. So policy store
// should not succeed. The error means that authpolicyd cached device policy
// and stores it in the next call to RefreshDevicePolicy in STEP_STORE_POLICY.
DCHECK(error != authpolicy::ERROR_NONE);
if (error == authpolicy::ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT)
error = authpolicy::ERROR_NONE;
std::move(callback).Run(error, machine_domain);
}
void AuthPolicyLoginHelper::OnAuthCallback(
AuthCallback callback,
authpolicy::ErrorType error,
const authpolicy::ActiveDirectoryAccountInfo& account_info) {
std::move(callback).Run(error, account_info);
}
AuthPolicyLoginHelper::~AuthPolicyLoginHelper() = default;
} // namespace chromeos