| // Copyright 2019 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 "chrome/credential_provider/gaiacp/password_recovery_manager.h" |
| |
| #include <windows.h> |
| #include <winternl.h> |
| |
| #include <lm.h> // Needed for LSA_UNICODE_STRING |
| #include <process.h> |
| |
| #define _NTDEF_ // Prevent redefition errors, must come after <winternl.h> |
| #include <ntsecapi.h> // For POLICY_ALL_ACCESS types |
| |
| #include "base/base64.h" |
| #include "base/containers/span.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/credential_provider/common/gcp_strings.h" |
| #include "chrome/credential_provider/gaiacp/gcp_utils.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| #include "chrome/credential_provider/gaiacp/mdm_utils.h" |
| #include "chrome/credential_provider/gaiacp/reg_utils.h" |
| #include "chrome/credential_provider/gaiacp/scoped_lsa_policy.h" |
| #include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h" |
| #include "crypto/aead.h" |
| #include "third_party/boringssl/src/include/openssl/aead.h" |
| #include "third_party/boringssl/src/include/openssl/bytestring.h" |
| #include "third_party/boringssl/src/include/openssl/err.h" |
| #include "third_party/boringssl/src/include/openssl/evp.h" |
| #include "third_party/boringssl/src/include/openssl/rand.h" |
| #include "third_party/boringssl/src/include/openssl/rsa.h" |
| #include "third_party/boringssl/src/include/openssl/x509.h" |
| |
| namespace credential_provider { |
| |
| const base::TimeDelta |
| PasswordRecoveryManager::kDefaultEscrowServiceRequestTimeout = |
| base::TimeDelta::FromMilliseconds(3000); |
| |
| namespace { |
| |
| typedef std::vector<std::pair<std::string, std::string*>> |
| UrlFetchResultNeedOutputs; |
| |
| // Constants for storing password recovery information in the LSA. |
| constexpr char kUserPasswordLsaStoreIdKey[] = "resource_id"; |
| constexpr char kUserPasswordLsaStoreEncryptedPasswordKey[] = |
| "encrypted_password"; |
| |
| // Constants used for contacting the password escrow service. |
| const char kEscrowServiceGenerateKeyPairPath[] = "/v1/generatekeypair"; |
| const char kGenerateKeyPairRequestDeviceIdParameterName[] = "deviceId"; |
| const char kGenerateKeyPairResponsePublicKeyParameterName[] = "base64PublicKey"; |
| const char kGenerateKeyPairResponseResourceIdParameterName[] = "resourceId"; |
| |
| const char kEscrowServiceGetPrivateKeyPath[] = "/v1/getprivatekey"; |
| const char kGetPrivateKeyResponsePrivateKeyParameterName[] = "base64PrivateKey"; |
| |
| constexpr wchar_t kUserPasswordLsaStoreKeyPrefix[] = |
| #if defined(GOOGLE_CHROME_BUILD) |
| L"Chrome-GCPW-"; |
| #else |
| L"Chromium-GCPW-"; |
| #endif |
| |
| // Constants used during padding and unpadding given secret. |
| constexpr char kPaddedPassword[] = "password"; |
| |
| constexpr char kPasswordLength[] = "password_length"; |
| |
| constexpr char kPaddingChar = '-'; |
| |
| constexpr size_t kMinPaddedPasswordLength = 64; |
| |
| // Constants used during encrypting and decrypting given secret. |
| constexpr size_t kNonceLength = 12; |
| |
| constexpr size_t kSessionKeyLength = 32; |
| |
| // Self deleting escrow service requester. This class will try to make a query |
| // using the given url fetcher. It will delete itself when the request is |
| // completed, either because the request completed successfully within the |
| // timeout or the request has timed out and is allowed to complete in the |
| // background without having the result read by anyone. |
| // There are two situations where the request will be deleted: |
| // 1. If the background thread making the request returns within the given |
| // timeout, the function is guaranteed to return the result that was fetched. |
| // 2. If however the background thread times out there are two potential |
| // race conditions that can occur: |
| // 1. The main thread making the request can mark that the background thread |
| // is orphaned before it can complete. In this case when the background |
| // thread completes it will check whether the request is orphaned and self |
| // delete. |
| // 2. The background thread completes before the main thread can mark the |
| // request as orphaned. In this case the background thread will have |
| // marked that the request is no longer processing and thus the main |
| // thread can self delete. |
| class EscrowServiceRequest { |
| public: |
| explicit EscrowServiceRequest(std::unique_ptr<WinHttpUrlFetcher> fetcher) |
| : fetcher_(std::move(fetcher)) { |
| DCHECK(fetcher_); |
| } |
| |
| // Tries to fetch the request stored in |fetcher_| in a background thread |
| // within the given |request_timeout|. If the background thread returns before |
| // the timeout expires, it is guaranteed that a result can be returned and the |
| // requester will delete itself. |
| base::Optional<base::Value> WaitForResponseFromEscrowService( |
| const base::TimeDelta& request_timeout) { |
| base::Optional<base::Value> result; |
| |
| // Start the thread and wait on its handle until |request_timeout| expires |
| // or the thread finishes. |
| unsigned wait_thread_id; |
| uintptr_t wait_thread = ::_beginthreadex( |
| nullptr, 0, &EscrowServiceRequest::FetchResultFromEscrowService, |
| reinterpret_cast<void*>(this), 0, &wait_thread_id); |
| |
| HRESULT hr = S_OK; |
| if (wait_thread == 0) { |
| return result; |
| } else { |
| // Hold the handle in the scoped handle so that it can be immediately |
| // closed when the wait is complete allowing the thread to finish |
| // completely if needed. |
| base::win::ScopedHandle thread_handle( |
| reinterpret_cast<HANDLE>(wait_thread)); |
| hr = ::WaitForSingleObject(thread_handle.Get(), |
| request_timeout.InMilliseconds()); |
| } |
| |
| // The race condition starts here. It is possible that between the expiry of |
| // the timeout in the call for WaitForSingleObject and the call to |
| // OrphanRequest, the fetching thread could have finished. So there is a two |
| // part handshake. Either the background thread has called ProcessingDone |
| // in which case it has already passed its own check for |is_orphaned_| and |
| // the call to OrphanRequest should delete this object right now. Otherwise |
| // the background thread is still running and will be able to query the |
| // |is_orphaned_| state and delete the object after thread completion. |
| if (hr != WAIT_OBJECT_0) { |
| LOGFN(ERROR) << "Wait for response timed out or failed hr=" << putHR(hr); |
| OrphanRequest(); |
| return result; |
| } |
| |
| result = base::JSONReader::Read( |
| base::StringPiece(response_.data(), response_.size()), |
| base::JSON_ALLOW_TRAILING_COMMAS); |
| if (!result || !result->is_dict()) { |
| LOGFN(ERROR) << "Failed to read json result from server response"; |
| result.reset(); |
| } |
| |
| delete this; |
| return result; |
| } |
| |
| private: |
| void OrphanRequest() { |
| bool delete_self = false; |
| { |
| base::AutoLock locker(orphan_lock_); |
| CHECK(!is_orphaned_); |
| if (!is_processing_) { |
| delete_self = true; |
| } else { |
| is_orphaned_ = true; |
| } |
| } |
| |
| if (delete_self) |
| delete this; |
| } |
| |
| void ProcessingDone() { |
| bool delete_self = false; |
| { |
| base::AutoLock locker(orphan_lock_); |
| CHECK(is_processing_); |
| if (is_orphaned_) { |
| delete_self = true; |
| } else { |
| is_processing_ = false; |
| } |
| } |
| |
| if (delete_self) |
| delete this; |
| } |
| |
| // Background thread function that is used to query the request to the |
| // escrow service. This thread never times out and simply marks the fetcher |
| // as finished processing when it is done. |
| static unsigned __stdcall FetchResultFromEscrowService(void* param) { |
| DCHECK(param); |
| EscrowServiceRequest* requester = |
| reinterpret_cast<EscrowServiceRequest*>(param); |
| |
| HRESULT hr = requester->fetcher_->Fetch(&requester->response_); |
| if (FAILED(hr)) |
| LOGFN(INFO) << "fetcher.Fetch hr=" << putHR(hr); |
| |
| requester->ProcessingDone(); |
| return 0; |
| } |
| |
| base::Lock orphan_lock_; |
| std::unique_ptr<WinHttpUrlFetcher> fetcher_; |
| std::vector<char> response_; |
| bool is_orphaned_ = false; |
| bool is_processing_ = true; |
| }; |
| |
| // Builds the required json request to be sent to the escrow service and fetches |
| // the json response from the escrow service (if any). Returns S_OK if |
| // |needed_outputs| can be filled correctly with the requested data, otherwise |
| // returns an error code. |
| // |request_url| is the full query url from which to fetch a response. |
| // |headers| are all the header key value pairs to be sent with the request. |
| // |parameters| are all the json parameters to be sent with the request. This |
| // argument will be converted to a json string and sent as part of the body of |
| // the request. |
| // |request_timeout| is the maximum time to wait for a response. |
| // |needed_outputs| is the mapping of the desired result key to an address where |
| // the result can be stored. |
| // If any |needed_outputs| is missing, all of the outputs are cleared. |
| HRESULT BuildRequestAndFetchResultFromEscrowService( |
| const GURL& request_url, |
| const std::vector<std::pair<std::string, std::string>>& headers, |
| const std::vector<std::pair<std::string, std::string>>& parameters, |
| const UrlFetchResultNeedOutputs& needed_outputs, |
| const base::TimeDelta& request_timeout) { |
| DCHECK(needed_outputs.size()); |
| |
| if (request_url.is_empty()) { |
| LOGFN(ERROR) << "No escrow service url specified"; |
| return E_FAIL; |
| } |
| |
| auto url_fetcher = WinHttpUrlFetcher::Create(request_url); |
| if (!url_fetcher) { |
| LOGFN(ERROR) << "Could not create valid fetcher for url=" |
| << request_url.spec(); |
| return E_FAIL; |
| } |
| |
| url_fetcher->SetRequestHeader("Content-Type", "application/json"); |
| |
| for (auto& header : headers) |
| url_fetcher->SetRequestHeader(header.first.c_str(), header.second.c_str()); |
| |
| HRESULT hr = S_OK; |
| |
| if (!parameters.empty()) { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| |
| for (auto& parameter : parameters) |
| request_dict.SetStringKey(parameter.first, parameter.second); |
| |
| std::string json; |
| if (!base::JSONWriter::Write(request_dict, &json)) { |
| LOGFN(ERROR) << "base::JSONWriter::Write failed"; |
| return E_FAIL; |
| } |
| |
| hr = url_fetcher->SetRequestBody(json.c_str()); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "fetcher.SetRequestBody hr=" << putHR(hr); |
| return E_FAIL; |
| } |
| } |
| |
| base::Optional<base::Value> request_result = |
| (new EscrowServiceRequest(std::move(url_fetcher))) |
| ->WaitForResponseFromEscrowService(request_timeout); |
| |
| if (!request_result) |
| return E_FAIL; |
| |
| for (const std::pair<std::string, std::string*>& output : needed_outputs) { |
| const std::string* output_value = |
| request_result->FindStringKey(output.first); |
| if (!output_value) { |
| LOGFN(ERROR) << "Could not extract value '" << output.first |
| << "' from server response"; |
| hr = E_FAIL; |
| break; |
| } |
| DCHECK(output.second); |
| *output.second = *output_value; |
| } |
| |
| if (FAILED(hr)) { |
| for (const std::pair<std::string, std::string*>& output : needed_outputs) |
| output.second->clear(); |
| } |
| |
| return hr; |
| } |
| |
| // Makes a standard: "Authorization: Bearer $TOKEN" header for passing |
| // authorization information to a server. |
| std::pair<std::string, std::string> MakeAuthorizationHeader( |
| const std::string& access_token) { |
| return {"Authorization", "Bearer " + access_token}; |
| } |
| |
| bool Base64DecodeCryptographicKey(const std::string& cryptographic_key, |
| std::string* out) { |
| std::string cryptographic_key_copy; |
| base::RemoveChars(cryptographic_key, "\n", &cryptographic_key_copy); |
| if (!base::Base64Decode(cryptographic_key_copy, out)) { |
| LOGFN(ERROR) << "Base64Decode failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| // Callback to log password encryption/decryption errors. |
| static int LogBoringSSLError(const char* str, size_t len, void* ctx) { |
| LOGFN(ERROR) << base::StringPiece(str, len); |
| return 1; |
| } |
| |
| // PadSecret pads the given |secret| with kPaddingChar and serializes the padded |
| // secret into JSON along with original secret length. |
| bool PadSecret(const std::string& secret, std::string* out) { |
| size_t padded_length = (secret.size() + kMinPaddedPasswordLength - 1) & |
| ~(kMinPaddedPasswordLength - 1); |
| std::string padded_secret(padded_length, kPaddingChar); |
| std::memcpy(&padded_secret[padded_length - secret.size()], secret.data(), |
| secret.size()); |
| |
| base::Value pwd_padding_dict(base::Value::Type::DICTIONARY); |
| pwd_padding_dict.SetStringKey(kPaddedPassword, padded_secret); |
| pwd_padding_dict.SetIntKey(kPasswordLength, secret.size()); |
| SecurelyClearString(padded_secret); |
| |
| auto result = base::JSONWriter::Write(pwd_padding_dict, out); |
| const std::string* password_value = |
| pwd_padding_dict.FindStringKey(kPaddedPassword); |
| if (password_value) |
| SecurelyClearString(*const_cast<std::string*>(password_value)); |
| |
| return result; |
| } |
| |
| // UnpadSecret deserializes given |padded_secret| into json object and tries to |
| // find padded secret. It then removes the padding and returns original secret. |
| bool UnpadSecret(const std::string& serialized_padded_secret, |
| std::string* out) { |
| base::Optional<base::Value> pwd_padding_dict = base::JSONReader::Read( |
| serialized_padded_secret, base::JSON_ALLOW_TRAILING_COMMAS); |
| if (!pwd_padding_dict.has_value() || !pwd_padding_dict->is_dict()) { |
| LOGFN(ERROR) << "Failed to deserialize given secret from json."; |
| return false; |
| } |
| |
| auto* padded_secret = pwd_padding_dict->FindStringKey(kPaddedPassword); |
| auto pwd_length = pwd_padding_dict->FindIntKey(kPasswordLength); |
| |
| auto result = true; |
| if (!padded_secret || !pwd_length.has_value()) { |
| result = false; |
| } else { |
| out->assign(&(*padded_secret)[padded_secret->size() - *pwd_length], |
| *pwd_length); |
| } |
| SecurelyClearDictionaryValueWithKey(&pwd_padding_dict, kPaddedPassword); |
| |
| return result; |
| } |
| |
| // Encrypts the given |secret| with the provided |public_key|. Returns a vector |
| // of uint8_t as the encrypted secret. |
| base::Optional<std::vector<uint8_t>> PublicKeyEncrypt( |
| const std::string& public_key, |
| const std::string& secret) { |
| CBS pub_key_cbs; |
| CBS_init(&pub_key_cbs, reinterpret_cast<const uint8_t*>(&public_key[0]), |
| public_key.size()); |
| bssl::UniquePtr<EVP_PKEY> pub_key(EVP_parse_public_key(&pub_key_cbs)); |
| if (!pub_key || CBS_len(&pub_key_cbs)) { |
| ERR_print_errors_cb(&LogBoringSSLError, /*unused*/ nullptr); |
| return base::nullopt; |
| } |
| |
| RSA* rsa = EVP_PKEY_get0_RSA(pub_key.get()); |
| if (!rsa) { |
| ERR_print_errors_cb(&LogBoringSSLError, /*unused*/ nullptr); |
| return base::nullopt; |
| } |
| |
| // Generate a random session key and random nonce. |
| uint8_t session_key_with_nonce[kSessionKeyLength + kNonceLength]; |
| RAND_bytes(session_key_with_nonce, sizeof(session_key_with_nonce)); |
| |
| // Encrypt the session key with the RSA public key. |
| size_t rsa_len; |
| std::vector<uint8_t> ciphertext(RSA_size(rsa)); |
| if (!RSA_encrypt(rsa, &rsa_len, ciphertext.data(), ciphertext.size(), |
| session_key_with_nonce, sizeof(session_key_with_nonce), |
| RSA_PKCS1_OAEP_PADDING)) { |
| ERR_print_errors_cb(&LogBoringSSLError, /*unused*/ nullptr); |
| return base::nullopt; |
| } |
| |
| std::string session_key(session_key_with_nonce, |
| session_key_with_nonce + kSessionKeyLength); |
| |
| std::string sealed_secret; |
| crypto::Aead aead(crypto::Aead::AES_256_GCM); |
| aead.Init(&session_key); |
| aead.Seal(secret, |
| base::StringPiece(reinterpret_cast<const char*>( |
| &session_key_with_nonce[kSessionKeyLength]), |
| kNonceLength), |
| /*ad=*/nullptr, &sealed_secret); |
| |
| ciphertext.insert(ciphertext.end(), sealed_secret.data(), |
| sealed_secret.data() + sealed_secret.size()); |
| return ciphertext; |
| } |
| |
| // Decrypts the provided |ciphertext| with the given |private_key|. Returns |
| // an base::Optional<std::string> as the decrypted secret. |
| base::Optional<std::string> PrivateKeyDecrypt( |
| const std::string& private_key, |
| base::span<const uint8_t> ciphertext) { |
| CBS priv_key_cbs; |
| CBS_init(&priv_key_cbs, reinterpret_cast<const uint8_t*>(&private_key[0]), |
| private_key.size()); |
| bssl::UniquePtr<EVP_PKEY> priv_key(EVP_parse_private_key(&priv_key_cbs)); |
| if (!priv_key || CBS_len(&priv_key_cbs)) { |
| ERR_print_errors_cb(&LogBoringSSLError, /*unused*/ nullptr); |
| return base::nullopt; |
| } |
| |
| RSA* rsa = EVP_PKEY_get0_RSA(priv_key.get()); |
| if (!rsa) { |
| LOGFN(ERROR) << "No RSA is found in EVP_PKEY_get0_RSA"; |
| return base::nullopt; |
| } |
| const size_t rsa_size = RSA_size(rsa); |
| if (ciphertext.size() < rsa_size) { |
| LOGFN(ERROR) << "Incorrect RSA size for given cipher text"; |
| return base::nullopt; |
| } |
| |
| // Decrypt the encrypted session key using given provided key. |
| std::vector<uint8_t> session_key_with_nonce(rsa_size); |
| size_t session_key_with_nonce_len; |
| if (!RSA_decrypt(rsa, &session_key_with_nonce_len, |
| session_key_with_nonce.data(), session_key_with_nonce.size(), |
| ciphertext.data(), rsa_size, RSA_PKCS1_OAEP_PADDING)) { |
| ERR_print_errors_cb(&LogBoringSSLError, /*unused*/ nullptr); |
| return base::nullopt; |
| } |
| session_key_with_nonce.resize(session_key_with_nonce_len); |
| |
| std::string session_key(session_key_with_nonce.data(), |
| session_key_with_nonce.data() + kSessionKeyLength); |
| |
| std::string plaintext; |
| crypto::Aead aead(crypto::Aead::AES_256_GCM); |
| aead.Init(&session_key); |
| aead.Open( |
| base::StringPiece(reinterpret_cast<const char*>(&ciphertext[rsa_size]), |
| ciphertext.size() - rsa_size), |
| base::StringPiece(reinterpret_cast<const char*>( |
| &session_key_with_nonce[kSessionKeyLength]), |
| kNonceLength), |
| /*ad=*/nullptr, &plaintext); |
| |
| return plaintext; |
| } |
| |
| // Request a new public key and corresponding resource id from the escrow |
| // service in order to encrypt |password|. |access_token| is used to authorize |
| // the request on the escrow service. |device_id| is used to identify the device |
| // making the request. Fills in |encrypted_data| the resource id for the |
| // encryption key and also with the encryped password. |
| HRESULT EncryptUserPasswordUsingEscrowService( |
| const std::string& access_token, |
| const std::string& device_id, |
| const base::string16& password, |
| const base::TimeDelta& request_timeout, |
| base::Optional<base::Value>* encrypted_data) { |
| DCHECK(encrypted_data); |
| DCHECK(!(*encrypted_data)); |
| |
| std::string resource_id; |
| std::string public_key; |
| |
| // Fetch the results and extract the |resource_id| for the key and the |
| // |public_key| to be used for encryption. |
| HRESULT hr = BuildRequestAndFetchResultFromEscrowService( |
| PasswordRecoveryManager::Get()->GetEscrowServiceGenerateKeyPairUrl(), |
| {MakeAuthorizationHeader(access_token)}, |
| {{kGenerateKeyPairRequestDeviceIdParameterName, device_id}}, |
| { |
| {kGenerateKeyPairResponseResourceIdParameterName, &resource_id}, |
| {kGenerateKeyPairResponsePublicKeyParameterName, &public_key}, |
| }, |
| request_timeout); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "BuildRequestAndFetchResultFromEscrowService hr=" |
| << putHR(hr); |
| return E_FAIL; |
| } |
| |
| std::string decoded_public_key; |
| if (!Base64DecodeCryptographicKey(public_key, &decoded_public_key)) { |
| LOGFN(ERROR) << "Failed to base64 decode public key"; |
| return E_FAIL; |
| } |
| |
| std::string password_utf8 = base::UTF16ToUTF8(password); |
| std::string padded_password; |
| auto result = PadSecret(password_utf8, &padded_password); |
| SecurelyClearString(password_utf8); |
| if (!result) { |
| LOGFN(ERROR) << "Failed while padding password"; |
| return E_FAIL; |
| } |
| |
| auto opt = PublicKeyEncrypt(decoded_public_key, padded_password); |
| SecurelyClearString(padded_password); |
| if (opt == base::nullopt) |
| return E_FAIL; |
| |
| encrypted_data->emplace(base::Value(base::Value::Type::DICTIONARY)); |
| (*encrypted_data)->SetStringKey(kUserPasswordLsaStoreIdKey, resource_id); |
| |
| std::string cipher_text; |
| base::Base64Encode( |
| base::StringPiece(reinterpret_cast<const char*>(opt->data()), |
| opt->size()), |
| &cipher_text); |
| (*encrypted_data) |
| ->SetStringKey(kUserPasswordLsaStoreEncryptedPasswordKey, cipher_text); |
| |
| return hr; |
| } |
| |
| // Given the |encrypted_data| which would contain the resource id of the |
| // encryption key and the encrypted password, recovers the |decrypted_password| |
| // by getting the private key from the escrow service and decrypting the |
| // password. |access_token| is used to authorize the request on the escrow |
| // service. |
| HRESULT DecryptUserPasswordUsingEscrowService( |
| const std::string& access_token, |
| const base::Optional<base::Value>& encrypted_data, |
| const base::TimeDelta& request_timeout, |
| base::string16* decrypted_password) { |
| if (!encrypted_data) |
| return E_FAIL; |
| DCHECK(decrypted_password); |
| DCHECK(encrypted_data && encrypted_data->is_dict()); |
| const std::string* resource_id = |
| encrypted_data->FindStringKey(kUserPasswordLsaStoreIdKey); |
| const std::string* encoded_cipher_text = |
| encrypted_data->FindStringKey(kUserPasswordLsaStoreEncryptedPasswordKey); |
| |
| if (!resource_id) { |
| LOGFN(ERROR) << "No password resource id found to restore"; |
| return E_FAIL; |
| } |
| |
| if (!encoded_cipher_text) { |
| LOGFN(ERROR) << "No encrypted password found to restore"; |
| return E_FAIL; |
| } |
| |
| std::string private_key; |
| |
| // Fetch the results and extract the |private_key| to be used for decryption. |
| HRESULT hr = BuildRequestAndFetchResultFromEscrowService( |
| PasswordRecoveryManager::Get()->GetEscrowServiceGetPrivateKeyUrl( |
| *resource_id), |
| {MakeAuthorizationHeader(access_token)}, {}, |
| { |
| {kGetPrivateKeyResponsePrivateKeyParameterName, &private_key}, |
| }, |
| request_timeout); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "BuildRequestAndFetchResultFromEscrowService hr=" |
| << putHR(hr); |
| return E_FAIL; |
| } |
| |
| std::string decoded_cipher_text; |
| if (!base::Base64Decode(*encoded_cipher_text, &decoded_cipher_text)) { |
| LOGFN(ERROR) << "Failed to base64 decode ciphertext"; |
| return E_FAIL; |
| } |
| |
| std::string decoded_private_key; |
| if (!Base64DecodeCryptographicKey(private_key, &decoded_private_key)) { |
| LOGFN(ERROR) << "Failed to base64 decode private key"; |
| return E_FAIL; |
| } |
| |
| auto decrypted_secret = |
| PrivateKeyDecrypt(decoded_private_key, |
| base::as_bytes(base::make_span(decoded_cipher_text))); |
| |
| if (decrypted_secret == base::nullopt) |
| return E_FAIL; |
| |
| std::string unpadded; |
| UnpadSecret(*decrypted_secret, &unpadded); |
| *decrypted_password = base::UTF8ToUTF16(unpadded); |
| |
| SecurelyClearString(*decrypted_secret); |
| SecurelyClearString(unpadded); |
| |
| return S_OK; |
| } |
| |
| } // namespace |
| |
| // static |
| PasswordRecoveryManager* PasswordRecoveryManager::Get() { |
| return *GetInstanceStorage(); |
| } |
| |
| // static |
| PasswordRecoveryManager** PasswordRecoveryManager::GetInstanceStorage() { |
| static PasswordRecoveryManager instance(kDefaultEscrowServiceRequestTimeout); |
| static PasswordRecoveryManager* instance_storage = &instance; |
| |
| return &instance_storage; |
| } |
| |
| PasswordRecoveryManager::PasswordRecoveryManager( |
| base::TimeDelta request_timeout) |
| : request_timeout_(request_timeout) {} |
| |
| PasswordRecoveryManager::~PasswordRecoveryManager() = default; |
| |
| HRESULT PasswordRecoveryManager::GetUserPasswordLsaStoreKey( |
| const base::string16& sid, |
| base::string16* store_key) { |
| DCHECK(store_key); |
| DCHECK(sid.size()); |
| |
| *store_key = kUserPasswordLsaStoreKeyPrefix + sid; |
| return S_OK; |
| } |
| |
| HRESULT PasswordRecoveryManager::ClearUserRecoveryPassword( |
| const base::string16& sid) { |
| auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS); |
| |
| if (!policy) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "ScopedLsaPolicy::Create hr=" << putHR(hr); |
| return hr; |
| } |
| base::string16 store_key; |
| HRESULT hr = GetUserPasswordLsaStoreKey(sid, &store_key); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "GetUserPasswordLsaStoreKey hr=" << putHR(hr); |
| return hr; |
| } |
| return policy->RemovePrivateData(store_key.c_str()); |
| } |
| |
| HRESULT PasswordRecoveryManager::StoreWindowsPasswordIfNeeded( |
| const base::string16& sid, |
| const std::string& access_token, |
| const base::string16& password) { |
| if (!MdmPasswordRecoveryEnabled()) |
| return E_NOTIMPL; |
| |
| base::string16 machine_guid; |
| HRESULT hr = GetMachineGuid(&machine_guid); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "Failed to get machine GUID hr=" << putHR(hr); |
| return hr; |
| } |
| |
| std::string device_id = base::UTF16ToUTF8(machine_guid); |
| |
| auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS); |
| |
| if (!policy) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "ScopedLsaPolicy::Create hr=" << putHR(hr); |
| return hr; |
| } |
| |
| // See if a password key is already stored in the LSA for this user. |
| base::string16 store_key; |
| hr = GetUserPasswordLsaStoreKey(sid, &store_key); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "GetUserPasswordLsaStoreKey hr=" << putHR(hr); |
| return hr; |
| } |
| |
| // Only check if a value already exists for the user's password. The call to |
| // RetrievePrivateData always succeeds if the value exists, regardless of |
| // the size of the buffer passed in. It will merely copy whatever it can |
| // into the buffer. In this case we don't care about the contents and |
| // just want to check the existence of a value. |
| wchar_t password_lsa_data[32]; |
| hr = policy->RetrievePrivateData(store_key.c_str(), password_lsa_data, |
| base::size(password_lsa_data)); |
| if (SUCCEEDED(hr)) { |
| SecurelyClearBuffer(password_lsa_data, sizeof(password_lsa_data)); |
| return S_OK; |
| } |
| |
| base::Optional<base::Value> encrypted_dict; |
| hr = EncryptUserPasswordUsingEscrowService(access_token, device_id, password, |
| request_timeout_, &encrypted_dict); |
| if (SUCCEEDED(hr)) { |
| std::string lsa_value; |
| if (base::JSONWriter::Write(encrypted_dict.value(), &lsa_value)) { |
| base::string16 lsa_value16 = base::UTF8ToUTF16(lsa_value); |
| hr = policy->StorePrivateData(store_key.c_str(), lsa_value16.c_str()); |
| SecurelyClearString(lsa_value16); |
| SecurelyClearString(lsa_value); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "StorePrivateData hr=" << putHR(hr); |
| return hr; |
| } |
| } else { |
| LOGFN(ERROR) << "base::JSONWriter::Write failed"; |
| return E_FAIL; |
| } |
| |
| SecurelyClearDictionaryValueWithKey( |
| &encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey); |
| } else { |
| LOGFN(ERROR) << "EncryptUserPasswordUsingEscrowService hr=" << putHR(hr); |
| return E_FAIL; |
| } |
| return S_OK; |
| } |
| |
| HRESULT PasswordRecoveryManager::RecoverWindowsPasswordIfPossible( |
| const base::string16& sid, |
| const std::string& access_token, |
| base::string16* recovered_password) { |
| if (!MdmPasswordRecoveryEnabled()) |
| return E_NOTIMPL; |
| |
| DCHECK(recovered_password); |
| |
| auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS); |
| |
| if (!policy) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "ScopedLsaPolicy::Create hr=" << putHR(hr); |
| return hr; |
| } |
| |
| // See if a password key is already stored in the LSA for this user. |
| base::string16 store_key; |
| HRESULT hr = GetUserPasswordLsaStoreKey(sid, &store_key); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "GetUserPasswordLsaStoreKey hr=" << putHR(hr); |
| return hr; |
| } |
| |
| wchar_t password_lsa_data[1024]; |
| hr = policy->RetrievePrivateData(store_key.c_str(), password_lsa_data, |
| base::size(password_lsa_data)); |
| |
| if (FAILED(hr)) |
| LOGFN(ERROR) << "RetrievePrivateData hr=" << putHR(hr); |
| |
| std::string json_string = base::UTF16ToUTF8(password_lsa_data); |
| base::Optional<base::Value> encrypted_dict = |
| base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS); |
| SecurelyClearString(json_string); |
| SecurelyClearBuffer(password_lsa_data, sizeof(password_lsa_data)); |
| |
| base::string16 decrypted_password; |
| hr = DecryptUserPasswordUsingEscrowService( |
| access_token, encrypted_dict, request_timeout_, &decrypted_password); |
| |
| if (encrypted_dict) { |
| SecurelyClearDictionaryValueWithKey( |
| &encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey); |
| } |
| |
| if (SUCCEEDED(hr)) |
| *recovered_password = decrypted_password; |
| SecurelyClearString(decrypted_password); |
| |
| return hr; |
| } |
| |
| GURL PasswordRecoveryManager::GetEscrowServiceGenerateKeyPairUrl() { |
| if (!MdmPasswordRecoveryEnabled()) |
| return GURL(); |
| |
| GURL escrow_service_server = MdmEscrowServiceUrl(); |
| |
| if (escrow_service_server.is_empty()) { |
| LOGFN(ERROR) << "No escrow service server specified"; |
| return GURL(); |
| } |
| |
| return escrow_service_server.Resolve(kEscrowServiceGenerateKeyPairPath); |
| } |
| |
| GURL PasswordRecoveryManager::GetEscrowServiceGetPrivateKeyUrl( |
| const std::string& resource_id) { |
| if (!MdmPasswordRecoveryEnabled()) |
| return GURL(); |
| |
| GURL escrow_service_server = MdmEscrowServiceUrl(); |
| |
| if (escrow_service_server.is_empty()) { |
| LOGFN(ERROR) << "No escrow service server specified"; |
| return GURL(); |
| } |
| |
| return escrow_service_server.Resolve( |
| base::StrCat({kEscrowServiceGetPrivateKeyPath, "/", resource_id})); |
| } |
| |
| std::string PasswordRecoveryManager::MakeGenerateKeyPairResponseForTesting( |
| const std::string& public_key, |
| const std::string& resource_id) { |
| return base::StringPrintf( |
| R"({"%s": "%s", "%s": "%s"})", |
| kGenerateKeyPairResponsePublicKeyParameterName, public_key.c_str(), |
| kGenerateKeyPairResponseResourceIdParameterName, resource_id.c_str()); |
| } |
| |
| std::string PasswordRecoveryManager::MakeGetPrivateKeyResponseForTesting( |
| const std::string& private_key) { |
| return base::StringPrintf(R"({"%s": "%s"})", |
| kGetPrivateKeyResponsePrivateKeyParameterName, |
| private_key.c_str()); |
| } |
| |
| } // namespace credential_provider |