diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-18 01:34:54 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-18 01:34:54 +0000 |
commit | c523d20e1db8fb7fc003686f336b958605e8a58f (patch) | |
tree | 6995a795f122aa0abc6d71befb21149ed9232877 /components/os_crypt | |
parent | 77d2fa7ea388573238e8503ecbfa50974a5372f2 (diff) | |
download | chromium_src-c523d20e1db8fb7fc003686f336b958605e8a58f.zip chromium_src-c523d20e1db8fb7fc003686f336b958605e8a58f.tar.gz chromium_src-c523d20e1db8fb7fc003686f336b958605e8a58f.tar.bz2 |
components: Rename encryptor directory to os_crypt.
Joi suggested doing this rename in a follow up CL (
https://codereview.chromium.org/183953005/diff/20001/components/encryptor/encryptor_password_mac.h#newcode16).
So here it is.
BUG=341293
TEST=None, no functional changes.
R=joi@chromium.org,thestig@chromium.org,bcwhite@chromium.org
Review URL: https://codereview.chromium.org/200713005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257573 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/os_crypt')
-rw-r--r-- | components/os_crypt/DEPS | 4 | ||||
-rw-r--r-- | components/os_crypt/OWNERS | 1 | ||||
-rw-r--r-- | components/os_crypt/README | 4 | ||||
-rw-r--r-- | components/os_crypt/ie7_password_win.cc | 148 | ||||
-rw-r--r-- | components/os_crypt/ie7_password_win.h | 54 | ||||
-rw-r--r-- | components/os_crypt/ie7_password_win_unittest.cc | 90 | ||||
-rw-r--r-- | components/os_crypt/keychain_password_mac.h | 36 | ||||
-rw-r--r-- | components/os_crypt/keychain_password_mac.mm | 79 | ||||
-rw-r--r-- | components/os_crypt/keychain_password_mac_unittest.mm | 80 | ||||
-rw-r--r-- | components/os_crypt/os_crypt.h | 48 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_mac.mm | 155 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_posix.cc | 139 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_switches.cc | 17 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_switches.h | 26 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_unittest.cc | 144 | ||||
-rw-r--r-- | components/os_crypt/os_crypt_win.cc | 66 |
16 files changed, 1091 insertions, 0 deletions
diff --git a/components/os_crypt/DEPS b/components/os_crypt/DEPS new file mode 100644 index 0000000..d175bda --- /dev/null +++ b/components/os_crypt/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "-content", + "+crypto" +] diff --git a/components/os_crypt/OWNERS b/components/os_crypt/OWNERS new file mode 100644 index 0000000..1967bf5 --- /dev/null +++ b/components/os_crypt/OWNERS @@ -0,0 +1 @@ +thestig@chromium.org diff --git a/components/os_crypt/README b/components/os_crypt/README new file mode 100644 index 0000000..14bcf15 --- /dev/null +++ b/components/os_crypt/README @@ -0,0 +1,4 @@ +OSCrypt gives access to simple encryption and decryption of strings. + +On systems where available (currently Linux and Mac), this uses system +services to perform the encryption/decryption. diff --git a/components/os_crypt/ie7_password_win.cc b/components/os_crypt/ie7_password_win.cc new file mode 100644 index 0000000..01569b1 --- /dev/null +++ b/components/os_crypt/ie7_password_win.cc @@ -0,0 +1,148 @@ +// Copyright 2014 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 "components/os_crypt/ie7_password_win.h" + +#include <wincrypt.h> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace { + +// Structures that IE7/IE8 use to store a username/password. +// Some of the fields might have been incorrectly reverse engineered. +struct PreHeader { + DWORD pre_header_size; // Size of this header structure. Always 12. + DWORD header_size; // Size of the real Header: sizeof(Header) + + // item_count * sizeof(Entry); + DWORD data_size; // Size of the data referenced by the entries. +}; + +struct Header { + char wick[4]; // The string "WICK". I don't know what it means. + DWORD fixed_header_size; // The size of this structure without the entries: + // sizeof(Header). + DWORD item_count; // Number of entries. Should be even. + wchar_t two_letters[2]; // Two unknown bytes. + DWORD unknown[2]; // Two unknown DWORDs. +}; + +struct Entry { + DWORD offset; // Offset where the data referenced by this entry is + // located. + FILETIME time_stamp; // Timestamp when the password got added. + DWORD string_length; // The length of the data string. +}; + +// Main data structure. +struct PasswordEntry { + PreHeader pre_header; // Contains the size of the different sections. + Header header; // Contains the number of items. + Entry entry[1]; // List of entries containing a string. Even-indexed + // are usernames, odd are passwords. There may be + // several sets saved for a single url hash. +}; +} // namespace + +namespace ie7_password { + +bool GetUserPassFromData(const std::vector<unsigned char>& data, + std::vector<DecryptedCredentials>* credentials) { + const PasswordEntry* information = + reinterpret_cast<const PasswordEntry*>(&data.front()); + + // Some expected values. If it's not what we expect we don't even try to + // understand the data. + if (information->pre_header.pre_header_size != sizeof(PreHeader)) + return false; + + const int entry_count = information->header.item_count; + if (entry_count % 2) // Usernames and Passwords + return false; + + if (information->header.fixed_header_size != sizeof(Header)) + return false; + + const uint8* offset_to_data = &data[0] + + information->pre_header.header_size + + information->pre_header.pre_header_size; + + for (int i = 0; i < entry_count / 2; ++i) { + + const Entry* user_entry = &information->entry[2*i]; + const Entry* pass_entry = user_entry+1; + + DecryptedCredentials c; + c.username = reinterpret_cast<const wchar_t*>(offset_to_data + + user_entry->offset); + c.password = reinterpret_cast<const wchar_t*>(offset_to_data + + pass_entry->offset); + credentials->push_back(c); + } + return true; +} + +std::wstring GetUrlHash(const std::wstring& url) { + std::wstring lower_case_url = StringToLowerASCII(url); + // Get a data buffer out of our std::wstring to pass to SHA1HashString. + std::string url_buffer( + reinterpret_cast<const char*>(lower_case_url.c_str()), + (lower_case_url.size() + 1) * sizeof(wchar_t)); + std::string hash_bin = base::SHA1HashString(url_buffer); + + std::wstring url_hash; + + // Transform the buffer to an hexadecimal string. + unsigned char checksum = 0; + for (size_t i = 0; i < hash_bin.size(); ++i) { + // std::string gives signed chars, which mess with StringPrintf and + // check_sum. + unsigned char hash_byte = static_cast<unsigned char>(hash_bin[i]); + checksum += hash_byte; + url_hash += base::StringPrintf(L"%2.2X", static_cast<unsigned>(hash_byte)); + } + url_hash += base::StringPrintf(L"%2.2X", checksum); + + return url_hash; +} + +bool DecryptPasswords(const std::wstring& url, + const std::vector<unsigned char>& data, + std::vector<DecryptedCredentials>* credentials) { + std::wstring lower_case_url = StringToLowerASCII(url); + DATA_BLOB input = {0}; + DATA_BLOB output = {0}; + DATA_BLOB url_key = {0}; + + input.pbData = const_cast<unsigned char*>(&data.front()); + input.cbData = static_cast<DWORD>((data.size()) * + sizeof(std::string::value_type)); + + url_key.pbData = reinterpret_cast<unsigned char*>( + const_cast<wchar_t*>(lower_case_url.data())); + url_key.cbData = static_cast<DWORD>((lower_case_url.size() + 1) * + sizeof(std::wstring::value_type)); + + if (CryptUnprotectData(&input, NULL, &url_key, NULL, NULL, + CRYPTPROTECT_UI_FORBIDDEN, &output)) { + // Now that we have the decrypted information, we need to understand it. + std::vector<unsigned char> decrypted_data; + decrypted_data.resize(output.cbData); + memcpy(&decrypted_data.front(), output.pbData, output.cbData); + + GetUserPassFromData(decrypted_data, credentials); + + LocalFree(output.pbData); + return true; + } + + return false; +} + +} // namespace ie7_password diff --git a/components/os_crypt/ie7_password_win.h b/components/os_crypt/ie7_password_win.h new file mode 100644 index 0000000..459fd92 --- /dev/null +++ b/components/os_crypt/ie7_password_win.h @@ -0,0 +1,54 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_OS_CRYPT_IE7_PASSWORD_WIN_H_ +#define COMPONENTS_OS_CRYPT_IE7_PASSWORD_WIN_H_ + +#include <windows.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/time/time.h" + +// Contains the information read from the IE7/IE8 Storage2 key in the registry. +struct IE7PasswordInfo { + // Hash of the url. + std::wstring url_hash; + + // Encrypted data containing the username, password and some more undocumented + // fields. + std::vector<unsigned char> encrypted_data; + + // When the login was imported. + base::Time date_created; +}; + +namespace ie7_password { + +struct DecryptedCredentials { + std::wstring username; + std::wstring password; +}; + +// Parses a data structure to find passwords and usernames. +// The collection of bytes in |data| is interpreted as a special PasswordEntry +// structure. IE saves the login information as a binary dump of this structure. +// Credentials extracted from |data| end up in |credentials|. +bool GetUserPassFromData(const std::vector<unsigned char>& data, + std::vector<DecryptedCredentials>* credentials); + +// Decrypts usernames and passwords for a given data vector using the url as +// the key. +// Output ends up in |credentials|. +bool DecryptPasswords(const std::wstring& url, + const std::vector<unsigned char>& data, + std::vector<DecryptedCredentials>* credentials); + +// Returns the hash of a url. +std::wstring GetUrlHash(const std::wstring& url); + +} // namespace ie7_password + +#endif // COMPONENTS_OS_CRYPT_IE7_PASSWORD_WIN_H_ diff --git a/components/os_crypt/ie7_password_win_unittest.cc b/components/os_crypt/ie7_password_win_unittest.cc new file mode 100644 index 0000000..a17787c --- /dev/null +++ b/components/os_crypt/ie7_password_win_unittest.cc @@ -0,0 +1,90 @@ +// Copyright 2014 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 "components/os_crypt/ie7_password_win.h" + +#include <windows.h> + +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(IE7PasswordTest, GetUserPassword) { + // This is the unencrypted values of my keys under Storage2. + // The passwords have been manually changed to abcdef... but the size remains + // the same. + const unsigned char kData1[] = + "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01" + "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76" + "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00" + "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00" + "\x6c\x00\x00\x00"; + + const unsigned char kData2[] = + "\x0c\x00\x00\x00\x38\x00\x00\x00\x24\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\xa8\xea\xf4\xe5\x9f\x9a\xc8\x01" + "\x09\x00\x00\x00\x14\x00\x00\x00\xa8\xea\xf4\xe5" + "\x9f\x9a\xc8\x01\x07\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x69\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00" + "\x65\x00\x66\x00\x67\x00\x00\x00"; + + std::vector<unsigned char> decrypted_data1; + decrypted_data1.resize(arraysize(kData1)); + memcpy(&decrypted_data1.front(), kData1, sizeof(kData1)); + + std::vector<unsigned char> decrypted_data2; + decrypted_data2.resize(arraysize(kData2)); + memcpy(&decrypted_data2.front(), kData2, sizeof(kData2)); + + std::vector<ie7_password::DecryptedCredentials> credentials; + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data1, &credentials)); + ASSERT_EQ(1u, credentials.size()); + EXPECT_EQ(L"abcdefgh", credentials[0].username); + EXPECT_EQ(L"abcdefghijkl", credentials[0].password); + + credentials.clear(); + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data2, &credentials)); + ASSERT_EQ(1u, credentials.size()); + EXPECT_EQ(L"abcdefghi", credentials[0].username); + EXPECT_EQ(L"abcdefg", credentials[0].password); +} + +TEST(IE7PasswordTest, GetThreeUserPasswords) { + // Unencrypted binary data holding 3 sets of credentials + const unsigned char kData[] = + "\x0c\x00\x00\x00\x78\x00\x00\x00\x48\x00\x00\x00\x57\x49\x43\x4b\x18" + "\x00\x00\x00\x06\x00\x00\x00\x5c\x00\x55\x00\x01\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\xc3\xf2\x4b\xda\x1d\xc4\xce\x01\x04\x00\x00" + "\x00\x0a\x00\x00\x00\xc3\xf2\x4b\xda\x1d\xc4\xce\x01\x06\x00\x00\x00" + "\x18\x00\x00\x00\xbe\x5e\xe9\xe1\x1d\xc4\xce\x01\x04\x00\x00\x00\x22" + "\x00\x00\x00\xbe\x5e\xe9\xe1\x1d\xc4\xce\x01\x06\x00\x00\x00\x30\x00" + "\x00\x00\x07\x50\x1f\xe6\x1d\xc4\xce\x01\x04\x00\x00\x00\x3a\x00\x00" + "\x00\x07\x50\x1f\xe6\x1d\xc4\xce\x01\x06\x00\x00\x00\x71\x00\x77\x00" + "\x65\x00\x72\x00\x00\x00\x71\x00\x77\x00\x65\x00\x72\x00\x74\x00\x79" + "\x00\x00\x00\x61\x00\x73\x00\x64\x00\x66\x00\x00\x00\x61\x00\x73\x00" + "\x64\x00\x66\x00\x67\x00\x68\x00\x00\x00\x7a\x00\x78\x00\x63\x00\x76" + "\x00\x00\x00\x7a\x00\x78\x00\x63\x00\x76\x00\x62\x00\x6e\x00\x00\x00"; + + std::vector<unsigned char> decrypted_data; + decrypted_data.resize(arraysize(kData)); + memcpy(&decrypted_data.front(), kData, sizeof(kData)); + + std::vector<ie7_password::DecryptedCredentials> credentials; + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data, &credentials)); + ASSERT_EQ(3u, credentials.size()); + EXPECT_EQ(L"qwer", credentials[0].username); + EXPECT_EQ(L"qwerty", credentials[0].password); + EXPECT_EQ(L"asdf", credentials[1].username); + EXPECT_EQ(L"asdfgh", credentials[1].password); + EXPECT_EQ(L"zxcv", credentials[2].username); + EXPECT_EQ(L"zxcvbn", credentials[2].password); +} diff --git a/components/os_crypt/keychain_password_mac.h b/components/os_crypt/keychain_password_mac.h new file mode 100644 index 0000000..f044f04 --- /dev/null +++ b/components/os_crypt/keychain_password_mac.h @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_OS_CRYPT_KEYCHAIN_PASSWORD_MAC_H_ +#define COMPONENTS_OS_CRYPT_KEYCHAIN_PASSWORD_MAC_H_ + +#include <string> + +#include "base/macros.h" + +namespace crypto { +class AppleKeychain; +} + +class KeychainPassword { + public: + explicit KeychainPassword(const crypto::AppleKeychain& keychain) + : keychain_(keychain) { + } + + // Get the OSCrypt password for this system. If no password exists + // in the Keychain then one is generated, stored in the Mac keychain, and + // returned. + // If one exists then it is fetched from the Keychain and returned. + // If the user disallows access to the Keychain (or an error occurs) then an + // empty string is returned. + std::string GetPassword() const; + + private: + const crypto::AppleKeychain& keychain_; + + DISALLOW_COPY_AND_ASSIGN(KeychainPassword); +}; + +#endif // COMPONENTS_OS_CRYPT_KEYCHAIN_PASSWORD_MAC_H_ diff --git a/components/os_crypt/keychain_password_mac.mm b/components/os_crypt/keychain_password_mac.mm new file mode 100644 index 0000000..d731cff --- /dev/null +++ b/components/os_crypt/keychain_password_mac.mm @@ -0,0 +1,79 @@ +// Copyright 2014 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 "components/os_crypt/keychain_password_mac.h" + +#import <Security/Security.h> + +#include "base/base64.h" +#include "base/mac/mac_logging.h" +#include "base/rand_util.h" +#include "crypto/apple_keychain.h" + +using crypto::AppleKeychain; + +namespace { + +// Generates a random password and adds it to the Keychain. The added password +// is returned from the function. If an error occurs, an empty password is +// returned. +std::string AddRandomPasswordToKeychain(const AppleKeychain& keychain, + const std::string& service_name, + const std::string& account_name) { + // Generate a password with 128 bits of randomness. + const int kBytes = 128 / 8; + std::string password; + base::Base64Encode(base::RandBytesAsString(kBytes), &password); + void* password_data = + const_cast<void*>(static_cast<const void*>(password.data())); + + OSStatus error = keychain.AddGenericPassword(NULL, + service_name.size(), + service_name.data(), + account_name.size(), + account_name.data(), + password.size(), + password_data, + NULL); + + if (error != noErr) { + OSSTATUS_DLOG(ERROR, error) << "Keychain add failed"; + return std::string(); + } + + return password; +} + +} // namespace + +std::string KeychainPassword::GetPassword() const { + // These two strings ARE indeed user facing. But they are used to access + // the encryption keyword. So as to not lose encrypted data when system + // locale changes we DO NOT LOCALIZE. + const std::string service_name = "Chrome Safe Storage"; + const std::string account_name = "Chrome"; + + UInt32 password_length = 0; + void* password_data = NULL; + OSStatus error = keychain_.FindGenericPassword(NULL, + service_name.size(), + service_name.data(), + account_name.size(), + account_name.data(), + &password_length, + &password_data, + NULL); + + if (error == noErr) { + std::string password = + std::string(static_cast<char*>(password_data), password_length); + keychain_.ItemFreeContent(NULL, password_data); + return password; + } else if (error == errSecItemNotFound) { + return AddRandomPasswordToKeychain(keychain_, service_name, account_name); + } else { + OSSTATUS_DLOG(ERROR, error) << "Keychain lookup failed"; + return std::string(); + } +} diff --git a/components/os_crypt/keychain_password_mac_unittest.mm b/components/os_crypt/keychain_password_mac_unittest.mm new file mode 100644 index 0000000..4e879df --- /dev/null +++ b/components/os_crypt/keychain_password_mac_unittest.mm @@ -0,0 +1,80 @@ +// Copyright 2014 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 "components/os_crypt/keychain_password_mac.h" + +#include "crypto/mock_apple_keychain.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using crypto::MockAppleKeychain; + +// Test that if we have an existing password in the Keychain and we are +// authorized by the user to read it then we get it back correctly. +TEST(KeychainPasswordTest, FindPasswordSuccess) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(noErr); + KeychainPassword password(keychain); + EXPECT_FALSE(password.GetPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if we do not have an existing password in the Keychain then it +// gets added successfully and returned. +TEST(KeychainPasswordTest, FindPasswordNotFound) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecItemNotFound); + KeychainPassword password(keychain); + EXPECT_EQ(24U, password.GetPassword().length()); + EXPECT_TRUE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if get denied access by the user then we return an empty password. +// And we should not try to add one. +TEST(KeychainPasswordTest, FindPasswordNotAuthorized) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecAuthFailed); + KeychainPassword password(keychain); + EXPECT_TRUE(password.GetPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if some random other error happens then we return an empty +// password, and we should not try to add one. +TEST(KeychainPasswordTest, FindPasswordOtherError) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecNotAvailable); + KeychainPassword password(keychain); + EXPECT_TRUE(password.GetPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that subsequent additions to the keychain give different passwords. +TEST(KeychainPasswordTest, PasswordsDiffer) { + MockAppleKeychain keychain1; + keychain1.set_find_generic_result(errSecItemNotFound); + KeychainPassword encryptor_password1(keychain1); + std::string password1 = encryptor_password1.GetPassword(); + EXPECT_FALSE(password1.empty()); + EXPECT_TRUE(keychain1.called_add_generic()); + EXPECT_EQ(0, keychain1.password_data_count()); + + MockAppleKeychain keychain2; + keychain2.set_find_generic_result(errSecItemNotFound); + KeychainPassword encryptor_password2(keychain2); + std::string password2 = encryptor_password2.GetPassword(); + EXPECT_FALSE(password2.empty()); + EXPECT_TRUE(keychain2.called_add_generic()); + EXPECT_EQ(0, keychain2.password_data_count()); + + // And finally check that the passwords are different. + EXPECT_NE(password1, password2); +} + +} // namespace diff --git a/components/os_crypt/os_crypt.h b/components/os_crypt/os_crypt.h new file mode 100644 index 0000000..a75ccf9 --- /dev/null +++ b/components/os_crypt/os_crypt.h @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_OS_CRYPT_OS_CRYPT_H_ +#define COMPONENTS_OS_CRYPT_OS_CRYPT_H_ + +#include <string> + +#include "base/strings/string16.h" + +// The OSCrypt class gives access to simple encryption and decryption of +// strings. Note that on Mac, access to the system Keychain is required and +// these calls can block the current thread to collect user input. +class OSCrypt { + public: + // Encrypt a string16. The output (second argument) is really an array of + // bytes, but we're passing it back as a std::string. + static bool EncryptString16(const base::string16& plaintext, + std::string* ciphertext); + + // Decrypt an array of bytes obtained with EncryptString16 back into a + // string16. Note that the input (first argument) is a std::string, so you + // need to first get your (binary) data into a string. + static bool DecryptString16(const std::string& ciphertext, + base::string16* plaintext); + + // Encrypt a string. + static bool EncryptString(const std::string& plaintext, + std::string* ciphertext); + + // Decrypt an array of bytes obtained with EnctryptString back into a string. + // Note that the input (first argument) is a std::string, so you need to first + // get your (binary) data into a string. + static bool DecryptString(const std::string& ciphertext, + std::string* plaintext); + +#if defined(OS_MACOSX) + // For unit testing purposes we instruct the Encryptor to use a mock Keychain + // on the Mac. The default is to use the real Keychain. + static void UseMockKeychain(bool use_mock); +#endif + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(OSCrypt); +}; + +#endif // COMPONENTS_OS_CRYPT_OS_CRYPT_H_ diff --git a/components/os_crypt/os_crypt_mac.mm b/components/os_crypt/os_crypt_mac.mm new file mode 100644 index 0000000..09448d7 --- /dev/null +++ b/components/os_crypt/os_crypt_mac.mm @@ -0,0 +1,155 @@ +// Copyright 2014 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 "components/os_crypt/os_crypt.h" + +#include <CommonCrypto/CommonCryptor.h> // for kCCBlockSizeAES128 + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "components/os_crypt/keychain_password_mac.h" +#include "components/os_crypt/os_crypt_switches.h" +#include "crypto/apple_keychain.h" +#include "crypto/encryptor.h" +#include "crypto/symmetric_key.h" + +using crypto::AppleKeychain; + +namespace { + +// Salt for Symmetric key derivation. +const char kSalt[] = "saltysalt"; + +// Key size required for 128 bit AES. +const size_t kDerivedKeySizeInBits = 128; + +// Constant for Symmetic key derivation. +const size_t kEncryptionIterations = 1003; + +// TODO(dhollowa): Refactor to allow dependency injection of Keychain. +static bool use_mock_keychain = false; + +// Prefix for cypher text returned by current encryption version. We prefix +// the cypher text with this string so that future data migration can detect +// this and migrate to different encryption without data loss. +const char kEncryptionVersionPrefix[] = "v10"; + +// Generates a newly allocated SymmetricKey object based on the password found +// in the Keychain. The generated key is for AES encryption. Ownership of the +// key is passed to the caller. Returns NULL key in the case password access +// is denied or key generation error occurs. +crypto::SymmetricKey* GetEncryptionKey() { + static bool mock_keychain_command_line_flag = + CommandLine::ForCurrentProcess()->HasSwitch( + os_crypt::switches::kUseMockKeychain); + + std::string password; + if (use_mock_keychain || mock_keychain_command_line_flag) { + password = "mock_password"; + } else { + AppleKeychain keychain; + KeychainPassword encryptor_password(keychain); + password = encryptor_password.GetPassword(); + } + + if (password.empty()) + return NULL; + + std::string salt(kSalt); + + // Create an encryption key from our password and salt. + scoped_ptr<crypto::SymmetricKey> encryption_key( + crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES, + password, + salt, + kEncryptionIterations, + kDerivedKeySizeInBits)); + DCHECK(encryption_key.get()); + + return encryption_key.release(); +} + +} // namespace + +bool OSCrypt::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext); +} + +bool OSCrypt::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = base::UTF8ToUTF16(utf8); + return true; +} + +bool OSCrypt::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + if (plaintext.empty()) { + *ciphertext = std::string(); + return true; + } + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kCCBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Encrypt(plaintext, ciphertext)) + return false; + + // Prefix the cypher text with version information. + ciphertext->insert(0, kEncryptionVersionPrefix); + return true; +} + +bool OSCrypt::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + if (ciphertext.empty()) { + *plaintext = std::string(); + return true; + } + + // Check that the incoming cyphertext was indeed encrypted with the expected + // version. If the prefix is not found then we'll assume we're dealing with + // old data saved as clear text and we'll return it directly. + // Credit card numbers are current legacy data, so false match with prefix + // won't happen. + if (ciphertext.find(kEncryptionVersionPrefix) != 0) { + *plaintext = ciphertext; + return true; + } + + // Strip off the versioning prefix before decrypting. + std::string raw_ciphertext = + ciphertext.substr(strlen(kEncryptionVersionPrefix)); + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kCCBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Decrypt(raw_ciphertext, plaintext)) + return false; + + return true; +} + +void OSCrypt::UseMockKeychain(bool use_mock) { + use_mock_keychain = use_mock; +} + diff --git a/components/os_crypt/os_crypt_posix.cc b/components/os_crypt/os_crypt_posix.cc new file mode 100644 index 0000000..74e8db3 --- /dev/null +++ b/components/os_crypt/os_crypt_posix.cc @@ -0,0 +1,139 @@ +// Copyright 2014 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 "components/os_crypt/os_crypt.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "crypto/encryptor.h" +#include "crypto/symmetric_key.h" + +namespace { + +// Salt for Symmetric key derivation. +const char kSalt[] = "saltysalt"; + +// Key size required for 128 bit AES. +const size_t kDerivedKeySizeInBits = 128; + +// Constant for Symmetic key derivation. +const size_t kEncryptionIterations = 1; + +// Size of initialization vector for AES 128-bit. +const size_t kIVBlockSizeAES128 = 16; + +// Prefix for cypher text returned by obfuscation version. We prefix the +// cyphertext with this string so that future data migration can detect +// this and migrate to full encryption without data loss. +const char kObfuscationPrefix[] = "v10"; + +// Generates a newly allocated SymmetricKey object based a hard-coded password. +// Ownership of the key is passed to the caller. Returns NULL key if a key +// generation error occurs. +crypto::SymmetricKey* GetEncryptionKey() { + // We currently "obfuscate" by encrypting and decrypting with hard-coded + // password. We need to improve this password situation by moving a secure + // password into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + std::string password = "peanuts"; + std::string salt(kSalt); + + // Create an encryption key from our password and salt. + scoped_ptr<crypto::SymmetricKey> encryption_key( + crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES, + password, + salt, + kEncryptionIterations, + kDerivedKeySizeInBits)); + DCHECK(encryption_key.get()); + + return encryption_key.release(); +} + +} // namespace + +bool OSCrypt::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext); +} + +bool OSCrypt::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = base::UTF8ToUTF16(utf8); + return true; +} + +bool OSCrypt::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + // This currently "obfuscates" by encrypting with hard-coded password. + // We need to improve this password situation by moving a secure password + // into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + + if (plaintext.empty()) { + *ciphertext = std::string(); + return true; + } + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kIVBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Encrypt(plaintext, ciphertext)) + return false; + + // Prefix the cypher text with version information. + ciphertext->insert(0, kObfuscationPrefix); + return true; +} + +bool OSCrypt::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + // This currently "obfuscates" by encrypting with hard-coded password. + // We need to improve this password situation by moving a secure password + // into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + + if (ciphertext.empty()) { + *plaintext = std::string(); + return true; + } + + // Check that the incoming cyphertext was indeed encrypted with the expected + // version. If the prefix is not found then we'll assume we're dealing with + // old data saved as clear text and we'll return it directly. + // Credit card numbers are current legacy data, so false match with prefix + // won't happen. + if (ciphertext.find(kObfuscationPrefix) != 0) { + *plaintext = ciphertext; + return true; + } + + // Strip off the versioning prefix before decrypting. + std::string raw_ciphertext = ciphertext.substr(strlen(kObfuscationPrefix)); + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kIVBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Decrypt(raw_ciphertext, plaintext)) + return false; + + return true; +} diff --git a/components/os_crypt/os_crypt_switches.cc b/components/os_crypt/os_crypt_switches.cc new file mode 100644 index 0000000..6dffb39 --- /dev/null +++ b/components/os_crypt/os_crypt_switches.cc @@ -0,0 +1,17 @@ +// Copyright 2014 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 "components/os_crypt/os_crypt_switches.h" + +namespace os_crypt { +namespace switches { + +#if defined(OS_MACOSX) + +const char kUseMockKeychain[] = "use-mock-keychain"; + +#endif // OS_MACOSX + +} // namespace switches +} // namespace os_crypt diff --git a/components/os_crypt/os_crypt_switches.h b/components/os_crypt/os_crypt_switches.h new file mode 100644 index 0000000..5529446 --- /dev/null +++ b/components/os_crypt/os_crypt_switches.h @@ -0,0 +1,26 @@ +// Copyright 2014 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. +// +#ifndef COMPONENTS_OS_CRYPT_OS_CRYPT_SWITCHES_H_ +#define COMPONENTS_OS_CRYPT_OS_CRYPT_SWITCHES_H_ + +// Defines all the command-line switches used by the encryptor component. + +#include "build/build_config.h" + +namespace os_crypt { +namespace switches { + +#if defined(OS_MACOSX) + +// Uses mock keychain for testing purposes, which prevents blocking dialogs +// from causing timeouts. +extern const char kUseMockKeychain[]; + +#endif // OS_MACOSX + +} // namespace switches +} // namespace os_crypt + +#endif // COMPONENTS_OS_CRYPT_OS_CRYPT_SWITCHES_H_ diff --git a/components/os_crypt/os_crypt_unittest.cc b/components/os_crypt/os_crypt_unittest.cc new file mode 100644 index 0000000..ef9fde0 --- /dev/null +++ b/components/os_crypt/os_crypt_unittest.cc @@ -0,0 +1,144 @@ +// Copyright 2014 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 "components/os_crypt/os_crypt.h" + +#include <string> + +#include "base/compiler_specific.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class OSCryptTest : public testing::Test { + public: + OSCryptTest() {} + + virtual void SetUp() OVERRIDE { +#if defined(OS_MACOSX) + OSCrypt::UseMockKeychain(true); +#endif + } + + private: + DISALLOW_COPY_AND_ASSIGN(OSCryptTest); +}; + +TEST_F(OSCryptTest, String16EncryptionDecryption) { + base::string16 plaintext; + base::string16 result; + std::string utf8_plaintext; + std::string utf8_result; + std::string ciphertext; + + // Test borderline cases (empty strings). + EXPECT_TRUE(OSCrypt::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = base::ASCIIToUTF16("hello"); + EXPECT_TRUE(OSCrypt::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a 16-byte aligned string. This previously hit a boundary error in + // base::OSCrypt::Crypt() on Mac. + plaintext = base::ASCIIToUTF16("1234567890123456"); + EXPECT_TRUE(OSCrypt::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test Unicode. + base::char16 wchars[] = { 0xdbeb, 0xdf1b, 0x4e03, 0x6708, 0x8849, + 0x661f, 0x671f, 0x56db, 0x597c, 0x4e03, + 0x6708, 0x56db, 0x6708, 0xe407, 0xdbaf, + 0xdeb5, 0x4ec5, 0x544b, 0x661f, 0x671f, + 0x65e5, 0x661f, 0x671f, 0x4e94, 0xd8b1, + 0xdce1, 0x7052, 0x5095, 0x7c0b, 0xe586, 0}; + plaintext = wchars; + utf8_plaintext = base::UTF16ToUTF8(plaintext); + EXPECT_EQ(plaintext, base::UTF8ToUTF16(utf8_plaintext)); + EXPECT_TRUE(OSCrypt::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &utf8_result)); + EXPECT_EQ(utf8_plaintext, base::UTF16ToUTF8(result)); + + EXPECT_TRUE(OSCrypt::EncryptString(utf8_plaintext, &ciphertext)); + EXPECT_TRUE(OSCrypt::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &utf8_result)); + EXPECT_EQ(utf8_plaintext, base::UTF16ToUTF8(result)); +} + +TEST_F(OSCryptTest, EncryptionDecryption) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test borderline cases (empty strings). + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = "hello"; + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Make sure it null terminates. + plaintext.assign("hello", 3); + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, "hel"); +} + +TEST_F(OSCryptTest, CypherTextDiffers) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test borderline cases (empty strings). + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + // |cyphertext| is empty on the Mac, different on Windows. + EXPECT_TRUE(ciphertext.empty() || plaintext != ciphertext); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = "hello"; + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, ciphertext); + EXPECT_EQ(plaintext, result); + + // Make sure it null terminates. + plaintext.assign("hello", 3); + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, ciphertext); + EXPECT_EQ(result, "hel"); +} + +TEST_F(OSCryptTest, DecryptError) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test a simple string, messing with ciphertext prior to decrypting. + plaintext = "hello"; + ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext)); + EXPECT_NE(plaintext, ciphertext); + ASSERT_LT(4UL, ciphertext.size()); + ciphertext[3] = ciphertext[3] + 1; + EXPECT_FALSE(OSCrypt::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, result); + EXPECT_TRUE(result.empty()); +} + +} // namespace diff --git a/components/os_crypt/os_crypt_win.cc b/components/os_crypt/os_crypt_win.cc new file mode 100644 index 0000000..1f4e749 --- /dev/null +++ b/components/os_crypt/os_crypt_win.cc @@ -0,0 +1,66 @@ +// Copyright 2014 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 "components/os_crypt/os_crypt.h" + +#include <windows.h> +#include <wincrypt.h> + +#include "base/strings/utf_string_conversions.h" + +#pragma comment(lib, "crypt32.lib") + +bool OSCrypt::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext); +} + +bool OSCrypt::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = base::UTF8ToUTF16(utf8); + return true; +} + +bool OSCrypt::EncryptString(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()); + + DATA_BLOB output; + BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL, + 0, &output); + if (!result) + return false; + + // this does a copy + ciphertext->assign(reinterpret_cast<std::string::value_type*>(output.pbData), + output.cbData); + + LocalFree(output.pbData); + return true; +} + +bool OSCrypt::DecryptString(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()); + + DATA_BLOB output; + BOOL result = CryptUnprotectData(&input, NULL, NULL, NULL, NULL, + 0, &output); + if (!result) + return false; + + plaintext->assign(reinterpret_cast<char*>(output.pbData), output.cbData); + LocalFree(output.pbData); + return true; +} |