| // 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 |