blob: 9fae41caa8567007563bda77427fb955ef963c1c [file] [log] [blame]
// Copyright (c) 2012 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/password_manager/core/browser/login_database.h"
#import <Security/Security.h>
#include <stddef.h>
#include <memory>
#include "base/base64.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/password_manager/core/common/passwords_directory_util_ios.h"
#include "sql/statement.h"
using base::ScopedCFTypeRef;
using autofill::PasswordForm;
namespace password_manager {
namespace {
void DeleteEncryptedPasswordFromKeychain(const std::string& cipher_text) {
if (cipher_text.empty())
return;
ScopedCFTypeRef<CFMutableDictionaryRef> query(
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
ScopedCFTypeRef<CFStringRef> item_ref(
base::SysUTF8ToCFStringRef(cipher_text));
// We are using the account attribute to store item references.
CFDictionarySetValue(query, kSecAttrAccount, item_ref);
OSStatus status = SecItemDelete(query);
if (status != errSecSuccess && status != errSecItemNotFound) {
NOTREACHED() << "Unable to remove password from keychain: " << status;
}
// Delete the temporary passwords directory, since there might be leftover
// temporary files used for password export that contain the password being
// deleted. It can be called for a removal triggered by sync, which might
// happen at the same time as an export operation. In the unlikely event
// that the file is still needed by the consumer app, the export operation
// will fail.
password_manager::DeletePasswordsDirectory();
}
} // namespace
// On iOS, the LoginDatabase uses Keychain API to store passwords. The
// "encrypted" version of the password is a unique ID (UUID) that is
// stored as an attribute along with the password in the keychain.
// A side effect of this approach is that the same password saved multiple
// times will have different "encrypted" values.
// TODO(ios): Use |Encryptor| to encrypt the login database. b/6976257
LoginDatabase::EncryptionResult LoginDatabase::EncryptedString(
const base::string16& plain_text,
std::string* cipher_text) const {
if (plain_text.size() == 0) {
*cipher_text = std::string();
return ENCRYPTION_RESULT_SUCCESS;
}
ScopedCFTypeRef<CFUUIDRef> uuid(CFUUIDCreate(NULL));
ScopedCFTypeRef<CFStringRef> item_ref(CFUUIDCreateString(NULL, uuid));
ScopedCFTypeRef<CFMutableDictionaryRef> attributes(
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(attributes, kSecClass, kSecClassGenericPassword);
// It does not matter which attribute we use to identify the keychain
// item as long as it uniquely identifies it. We are arbitrarily choosing the
// |kSecAttrAccount| attribute for this purpose.
CFDictionarySetValue(attributes, kSecAttrAccount, item_ref);
std::string plain_text_utf8 = base::UTF16ToUTF8(plain_text);
ScopedCFTypeRef<CFDataRef> data(
CFDataCreate(NULL, reinterpret_cast<const UInt8*>(plain_text_utf8.data()),
plain_text_utf8.size()));
CFDictionarySetValue(attributes, kSecValueData, data);
// Only allow access when the device has been unlocked.
CFDictionarySetValue(attributes, kSecAttrAccessible,
kSecAttrAccessibleWhenUnlocked);
OSStatus status = SecItemAdd(attributes, NULL);
if (status != errSecSuccess) {
NOTREACHED() << "Unable to save password in keychain: " << status;
if (status == errSecDuplicateItem || status == errSecDecode)
return ENCRYPTION_RESULT_ITEM_FAILURE;
else
return ENCRYPTION_RESULT_SERVICE_FAILURE;
}
*cipher_text = base::SysCFStringRefToUTF8(item_ref);
return ENCRYPTION_RESULT_SUCCESS;
}
LoginDatabase::EncryptionResult LoginDatabase::DecryptedString(
const std::string& cipher_text,
base::string16* plain_text) const {
if (cipher_text.size() == 0) {
*plain_text = base::string16();
return ENCRYPTION_RESULT_SUCCESS;
}
ScopedCFTypeRef<CFStringRef> item_ref(
base::SysUTF8ToCFStringRef(cipher_text));
ScopedCFTypeRef<CFMutableDictionaryRef> query(
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
// We are using the account attribute to store item references.
CFDictionarySetValue(query, kSecAttrAccount, item_ref);
CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
CFDataRef data;
OSStatus status = SecItemCopyMatching(query, (CFTypeRef*)&data);
if (status != errSecSuccess) {
OSSTATUS_LOG(INFO, status) << "Failed to retrieve password from keychain";
if (status == errSecItemNotFound || status == errSecDecode)
return ENCRYPTION_RESULT_ITEM_FAILURE;
else
return ENCRYPTION_RESULT_SERVICE_FAILURE;
}
const size_t size = CFDataGetLength(data);
std::unique_ptr<UInt8[]> buffer(new UInt8[size]);
CFDataGetBytes(data, CFRangeMake(0, size), buffer.get());
CFRelease(data);
*plain_text = base::UTF8ToUTF16(
std::string(static_cast<char*>(static_cast<void*>(buffer.get())),
static_cast<size_t>(size)));
return ENCRYPTION_RESULT_SUCCESS;
}
void LoginDatabase::DeleteEncryptedPassword(const PasswordForm& form) {
std::string cipher_text = GetEncryptedPassword(form);
DeleteEncryptedPasswordFromKeychain(cipher_text);
}
void LoginDatabase::DeleteEncryptedPasswordById(int id) {
std::string cipher_text = GetEncryptedPasswordById(id);
DeleteEncryptedPasswordFromKeychain(cipher_text);
}
std::string LoginDatabase::GetEncryptedPasswordById(int id) const {
DCHECK(!encrypted_password_statement_by_id_.empty());
sql::Statement s(db_.GetCachedStatement(
SQL_FROM_HERE, encrypted_password_statement_by_id_.c_str()));
s.BindInt(0, id);
std::string encrypted_password;
if (s.Step()) {
s.ColumnBlobAsString(0, &encrypted_password);
}
return encrypted_password;
}
} // namespace password_manager