| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/os_crypt/sync/os_crypt.h" |
| |
| #include <windows.h> |
| |
| #include "base/base64.h" |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/span.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/memory/singleton.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/wincrypt_shim.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/version_info/version_info.h" |
| #include "crypto/aead.h" |
| #include "crypto/hkdf.h" |
| #include "crypto/random.h" |
| |
| namespace { |
| |
| // Contains base64 random key encrypted with DPAPI. |
| constexpr char kOsCryptEncryptedKeyPrefName[] = "os_crypt.encrypted_key"; |
| |
| // Whether or not an attempt has been made to enable audit for the DPAPI |
| // encryption backing the random key. |
| constexpr char kOsCryptAuditEnabledPrefName[] = "os_crypt.audit_enabled"; |
| |
| // AEAD key length in bytes. |
| constexpr size_t kKeyLength = 256 / 8; |
| |
| // AEAD nonce length in bytes. |
| constexpr size_t kNonceLength = 96 / 8; |
| |
| // Version prefix for data encrypted with profile bound key. |
| constexpr char kEncryptionVersionPrefix[] = "v10"; |
| |
| // Key prefix for a key encrypted with DPAPI. |
| constexpr char kDPAPIKeyPrefix[] = "DPAPI"; |
| |
| bool EncryptStringWithDPAPI(const std::string& plaintext, |
| std::string* ciphertext) { |
| DATA_BLOB input; |
| input.pbData = |
| const_cast<BYTE*>(reinterpret_cast<const BYTE*>(plaintext.data())); |
| input.cbData = static_cast<DWORD>(plaintext.length()); |
| |
| BOOL result = FALSE; |
| DATA_BLOB output; |
| { |
| SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.Win.Encrypt.Time"); |
| result = ::CryptProtectData( |
| /*pDataIn=*/&input, |
| /*szDataDescr=*/ |
| base::SysUTF8ToWide( |
| base::StrCat( |
| {version_info::GetProductName(), |
| version_info::IsOfficialBuild() ? "" : " (Developer Build)"})) |
| .c_str(), |
| /*pOptionalEntropy=*/nullptr, |
| /*pvReserved=*/nullptr, |
| /*pPromptStruct=*/nullptr, /*dwFlags=*/CRYPTPROTECT_AUDIT, |
| /*pDataOut=*/&output); |
| } |
| base::UmaHistogramBoolean("OSCrypt.Win.Encrypt.Result", result); |
| if (!result) { |
| PLOG(ERROR) << "Failed to encrypt"; |
| return false; |
| } |
| |
| // this does a copy |
| ciphertext->assign(reinterpret_cast<std::string::value_type*>(output.pbData), |
| output.cbData); |
| |
| LocalFree(output.pbData); |
| return true; |
| } |
| |
| bool DecryptStringWithDPAPI(const std::string& ciphertext, |
| std::string* plaintext) { |
| DATA_BLOB input; |
| input.pbData = |
| const_cast<BYTE*>(reinterpret_cast<const BYTE*>(ciphertext.data())); |
| input.cbData = static_cast<DWORD>(ciphertext.length()); |
| |
| BOOL result = FALSE; |
| DATA_BLOB output; |
| { |
| SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.Win.Decrypt.Time"); |
| result = CryptUnprotectData(&input, nullptr, nullptr, nullptr, nullptr, 0, |
| &output); |
| } |
| base::UmaHistogramBoolean("OSCrypt.Win.Decrypt.Result", result); |
| if (!result) { |
| PLOG(ERROR) << "Failed to decrypt"; |
| return false; |
| } |
| |
| plaintext->assign(reinterpret_cast<char*>(output.pbData), output.cbData); |
| LocalFree(output.pbData); |
| return true; |
| } |
| |
| // Takes `key` and encrypts it with DPAPI, then stores it in the `local_state`. |
| // Returns true if the key was successfully encrypted and stored. |
| bool EncryptAndStoreKey(const std::string& key, PrefService* local_state) { |
| std::string encrypted_key; |
| if (!EncryptStringWithDPAPI(key, &encrypted_key)) { |
| return false; |
| } |
| |
| // Add header indicating this key is encrypted with DPAPI. |
| encrypted_key.insert(0, kDPAPIKeyPrefix); |
| std::string base64_key = base::Base64Encode(encrypted_key); |
| local_state->SetString(kOsCryptEncryptedKeyPrefName, base64_key); |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace OSCrypt { |
| bool EncryptString16(const std::u16string& plaintext, std::string* ciphertext) { |
| return OSCryptImpl::GetInstance()->EncryptString16(plaintext, ciphertext); |
| } |
| bool DecryptString16(const std::string& ciphertext, std::u16string* plaintext) { |
| return OSCryptImpl::GetInstance()->DecryptString16(ciphertext, plaintext); |
| } |
| bool EncryptString(const std::string& plaintext, std::string* ciphertext) { |
| return OSCryptImpl::GetInstance()->EncryptString(plaintext, ciphertext); |
| } |
| bool DecryptString(const std::string& ciphertext, std::string* plaintext) { |
| return OSCryptImpl::GetInstance()->DecryptString(ciphertext, plaintext); |
| } |
| void RegisterLocalPrefs(PrefRegistrySimple* registry) { |
| OSCryptImpl::RegisterLocalPrefs(registry); |
| } |
| InitResult InitWithExistingKey(PrefService* local_state) { |
| return OSCryptImpl::GetInstance()->InitWithExistingKey(local_state); |
| } |
| bool Init(PrefService* local_state) { |
| return OSCryptImpl::GetInstance()->Init(local_state); |
| } |
| std::string GetRawEncryptionKey() { |
| return OSCryptImpl::GetInstance()->GetRawEncryptionKey(); |
| } |
| void SetRawEncryptionKey(const std::string& key) { |
| OSCryptImpl::GetInstance()->SetRawEncryptionKey(key); |
| } |
| bool IsEncryptionAvailable() { |
| return OSCryptImpl::GetInstance()->IsEncryptionAvailable(); |
| } |
| void UseMockKeyForTesting(bool use_mock) { |
| OSCryptImpl::GetInstance()->UseMockKeyForTesting(use_mock); |
| } |
| void SetLegacyEncryptionForTesting(bool legacy) { |
| OSCryptImpl::GetInstance()->SetLegacyEncryptionForTesting(legacy); |
| } |
| void ResetStateForTesting() { |
| OSCryptImpl::GetInstance()->ResetStateForTesting(); |
| } |
| } // namespace OSCrypt |
| |
| OSCryptImpl::OSCryptImpl() = default; |
| OSCryptImpl::~OSCryptImpl() = default; |
| |
| OSCryptImpl* OSCryptImpl::GetInstance() { |
| return base::Singleton<OSCryptImpl, |
| base::LeakySingletonTraits<OSCryptImpl>>::get(); |
| } |
| |
| bool OSCryptImpl::EncryptString16(const std::u16string& plaintext, |
| std::string* ciphertext) { |
| return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext); |
| } |
| |
| bool OSCryptImpl::DecryptString16(const std::string& ciphertext, |
| std::u16string* plaintext) { |
| std::string utf8; |
| if (!DecryptString(ciphertext, &utf8)) |
| return false; |
| |
| *plaintext = base::UTF8ToUTF16(utf8); |
| return true; |
| } |
| |
| bool OSCryptImpl::EncryptString(const std::string& plaintext, |
| std::string* ciphertext) { |
| if (use_legacy_) |
| return EncryptStringWithDPAPI(plaintext, ciphertext); |
| |
| crypto::Aead aead(crypto::Aead::AES_256_GCM); |
| |
| const auto key = GetRawEncryptionKey(); |
| aead.Init(&key); |
| |
| // Note: can only check these once AEAD is initialized. |
| DCHECK_EQ(kKeyLength, aead.KeyLength()); |
| DCHECK_EQ(kNonceLength, aead.NonceLength()); |
| |
| std::string nonce(kNonceLength, '\0'); |
| crypto::RandBytes(base::as_writable_byte_span(nonce)); |
| |
| if (!aead.Seal(plaintext, nonce, std::string(), ciphertext)) |
| return false; |
| |
| ciphertext->insert(0, nonce); |
| ciphertext->insert(0, kEncryptionVersionPrefix); |
| return true; |
| } |
| |
| bool OSCryptImpl::DecryptString(const std::string& ciphertext, |
| std::string* plaintext) { |
| if (!base::StartsWith(ciphertext, kEncryptionVersionPrefix, |
| base::CompareCase::SENSITIVE)) |
| return DecryptStringWithDPAPI(ciphertext, plaintext); |
| |
| crypto::Aead aead(crypto::Aead::AES_256_GCM); |
| |
| const auto key = GetRawEncryptionKey(); |
| aead.Init(&key); |
| |
| // Obtain the nonce. |
| const std::string nonce = |
| ciphertext.substr(sizeof(kEncryptionVersionPrefix) - 1, kNonceLength); |
| // Strip off the versioning prefix before decrypting. |
| const std::string raw_ciphertext = |
| ciphertext.substr(kNonceLength + (sizeof(kEncryptionVersionPrefix) - 1)); |
| |
| return aead.Open(raw_ciphertext, nonce, std::string(), plaintext); |
| } |
| |
| // static |
| void OSCryptImpl::RegisterLocalPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(kOsCryptEncryptedKeyPrefName, ""); |
| registry->RegisterBooleanPref(kOsCryptAuditEnabledPrefName, false); |
| } |
| |
| bool OSCryptImpl::Init(PrefService* local_state) { |
| // Try to pull the key from the local state. |
| switch (InitWithExistingKey(local_state)) { |
| case OSCrypt::kSuccess: |
| return true; |
| case OSCrypt::kKeyDoesNotExist: |
| break; |
| case OSCrypt::kInvalidKeyFormat: |
| return false; |
| case OSCrypt::kDecryptionFailed: |
| break; |
| } |
| |
| // If there is no key in the local state, or if DPAPI decryption fails, |
| // generate a new key. |
| std::string key(kKeyLength, '\0'); |
| crypto::RandBytes(base::as_writable_byte_span(key)); |
| |
| if (!EncryptAndStoreKey(key, local_state)) { |
| return false; |
| } |
| |
| // This new key is already encrypted with audit flag enabled. |
| local_state->SetBoolean(kOsCryptAuditEnabledPrefName, true); |
| |
| encryption_key_.assign(key); |
| return true; |
| } |
| |
| OSCrypt::InitResult OSCryptImpl::InitWithExistingKey(PrefService* local_state) { |
| DCHECK(encryption_key_.empty()) << "Key already exists."; |
| // Try and pull the key from the local state. |
| if (!local_state->HasPrefPath(kOsCryptEncryptedKeyPrefName)) |
| return OSCrypt::kKeyDoesNotExist; |
| |
| const std::string base64_encrypted_key = |
| local_state->GetString(kOsCryptEncryptedKeyPrefName); |
| std::string encrypted_key_with_header; |
| |
| base::Base64Decode(base64_encrypted_key, &encrypted_key_with_header); |
| |
| if (!base::StartsWith(encrypted_key_with_header, kDPAPIKeyPrefix, |
| base::CompareCase::SENSITIVE)) { |
| return OSCrypt::kInvalidKeyFormat; |
| } |
| |
| const std::string encrypted_key = |
| encrypted_key_with_header.substr(sizeof(kDPAPIKeyPrefix) - 1); |
| std::string key; |
| // This DPAPI decryption can fail if the user's password has been reset |
| // by an Administrator. |
| if (!DecryptStringWithDPAPI(encrypted_key, &key)) { |
| base::UmaHistogramSparse("OSCrypt.Win.KeyDecryptionError", |
| ::GetLastError()); |
| return OSCrypt::kDecryptionFailed; |
| } |
| |
| if (!local_state->GetBoolean(kOsCryptAuditEnabledPrefName)) { |
| // In theory, EncryptAndStoreKey could fail if DPAPI fails to encrypt, but |
| // DPAPI decrypted the old data fine. In this case it's better to leave the |
| // previously encrypted key, since the code has been able to decrypt it. |
| // Trying over and over makes no sense so the code explicitly does not |
| // attempt again, and audit will simply not be enabled in this case. |
| std::ignore = EncryptAndStoreKey(key, local_state); |
| |
| // Indicate that an attempt has been made to turn audit flag on, so retry is |
| // not attempted. |
| local_state->SetBoolean(kOsCryptAuditEnabledPrefName, true); |
| } |
| encryption_key_.assign(key); |
| return OSCrypt::kSuccess; |
| } |
| |
| void OSCryptImpl::SetRawEncryptionKey(const std::string& raw_key) { |
| DCHECK(!use_mock_key_) << "Mock key in use."; |
| DCHECK(!raw_key.empty()) << "Bad key."; |
| DCHECK(encryption_key_.empty()) << "Key already set."; |
| encryption_key_.assign(raw_key); |
| } |
| |
| std::string OSCryptImpl::GetRawEncryptionKey() { |
| if (use_mock_key_) { |
| if (mock_encryption_key_.empty()) |
| mock_encryption_key_.assign( |
| crypto::HkdfSha256("peanuts", "salt", "info", kKeyLength)); |
| DCHECK(!mock_encryption_key_.empty()) << "Failed to initialize mock key."; |
| return mock_encryption_key_; |
| } |
| |
| DCHECK(!encryption_key_.empty()) << "No key."; |
| return encryption_key_; |
| } |
| |
| bool OSCryptImpl::IsEncryptionAvailable() { |
| if (use_mock_key_) { |
| return !GetRawEncryptionKey().empty(); |
| } |
| return !encryption_key_.empty(); |
| } |
| |
| void OSCryptImpl::UseMockKeyForTesting(bool use_mock) { |
| use_mock_key_ = use_mock; |
| } |
| |
| void OSCryptImpl::SetLegacyEncryptionForTesting(bool legacy) { |
| use_legacy_ = legacy; |
| } |
| |
| void OSCryptImpl::ResetStateForTesting() { |
| use_legacy_ = false; |
| use_mock_key_ = false; |
| encryption_key_.clear(); |
| mock_encryption_key_.clear(); |
| } |