| // Copyright 2018 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. |
| |
| #import "remoting/ios/persistence/remoting_keychain.h" |
| |
| #import <Security/Security.h> |
| |
| #include "base/logging.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/no_destructor.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| using ScopedMutableDictionary = base::ScopedCFTypeRef<CFMutableDictionaryRef>; |
| |
| const char kServicePrefix[] = "com.google.ChromeRemoteDesktop."; |
| |
| base::ScopedCFTypeRef<CFStringRef> WrapStdString(const std::string& str) { |
| // Note that kCFAllocatorDefault as allocator will not do anything, but |
| // kCFAllocatorDefault as deallocator will release the buffer, which leads to |
| // double-free because it's owned by the std::string. |
| return base::ScopedCFTypeRef<CFStringRef>( |
| CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, str.c_str(), |
| kCFStringEncodingUTF8, kCFAllocatorNull)); |
| } |
| |
| base::ScopedCFTypeRef<CFDataRef> WrapStringToData(const std::string& data) { |
| const UInt8* data_pointer = reinterpret_cast<const UInt8*>(data.data()); |
| return base::ScopedCFTypeRef<CFDataRef>(CFDataCreateWithBytesNoCopy( |
| kCFAllocatorDefault, data_pointer, data.size(), kCFAllocatorNull)); |
| } |
| |
| ScopedMutableDictionary CreateScopedMutableDictionary() { |
| return ScopedMutableDictionary(CFDictionaryCreateMutable( |
| kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| } |
| |
| ScopedMutableDictionary CreateQueryForUpdate(const std::string& service, |
| const std::string& account) { |
| ScopedMutableDictionary dictionary = CreateScopedMutableDictionary(); |
| CFDictionarySetValue(dictionary.get(), kSecClass, kSecClassGenericPassword); |
| CFDictionarySetValue(dictionary.get(), kSecAttrService, |
| WrapStdString(service).get()); |
| CFDictionarySetValue(dictionary.get(), kSecAttrAccount, |
| WrapStdString(account).get()); |
| |
| return dictionary; |
| } |
| |
| ScopedMutableDictionary CreateQueryForLookup(const std::string& service, |
| const std::string& account) { |
| ScopedMutableDictionary dictionary = CreateQueryForUpdate(service, account); |
| CFDictionarySetValue(dictionary.get(), kSecMatchLimit, kSecMatchLimitOne); |
| CFDictionarySetValue(dictionary.get(), kSecReturnData, kCFBooleanTrue); |
| return dictionary; |
| } |
| |
| ScopedMutableDictionary CreateDictionaryForInsertion(const std::string& service, |
| const std::string& account, |
| const std::string& data) { |
| ScopedMutableDictionary dictionary = CreateQueryForUpdate(service, account); |
| CFDictionarySetValue(dictionary.get(), kSecValueData, |
| WrapStringToData(data).get()); |
| return dictionary; |
| } |
| |
| } // namespace |
| |
| RemotingKeychain::RemotingKeychain() : service_prefix_(kServicePrefix) {} |
| |
| RemotingKeychain::~RemotingKeychain() {} |
| |
| // static |
| RemotingKeychain* RemotingKeychain::GetInstance() { |
| static base::NoDestructor<RemotingKeychain> instance; |
| return instance.get(); |
| } |
| |
| void RemotingKeychain::SetData(Key key, |
| const std::string& account, |
| const std::string& data) { |
| DCHECK(data.size()); |
| |
| std::string service = KeyToService(key); |
| |
| std::string existing_data = GetData(key, account); |
| if (!existing_data.empty()) { |
| // Item already exists. Update it. |
| |
| ScopedMutableDictionary update_query = |
| CreateQueryForUpdate(service, account); |
| |
| ScopedMutableDictionary updated_attributes = |
| CreateScopedMutableDictionary(); |
| CFDictionarySetValue(updated_attributes.get(), kSecValueData, |
| WrapStringToData(data).get()); |
| OSStatus status = SecItemUpdate(update_query, updated_attributes); |
| if (status != errSecSuccess) { |
| LOG(FATAL) << "Failed to update keychain item. Status: " << status; |
| } |
| return; |
| } |
| |
| // Item doesn't exist. Add it. |
| ScopedMutableDictionary insertion_dictionary = |
| CreateDictionaryForInsertion(service, account, data); |
| OSStatus status = SecItemAdd(insertion_dictionary.get(), NULL); |
| if (status != errSecSuccess) { |
| LOG(FATAL) << "Failed to add new keychain item. Status: " << status; |
| } |
| } |
| |
| std::string RemotingKeychain::GetData(Key key, |
| const std::string& account) const { |
| std::string service = KeyToService(key); |
| |
| ScopedMutableDictionary query = CreateQueryForLookup(service, account); |
| base::ScopedCFTypeRef<CFDataRef> cf_result; |
| OSStatus status = |
| SecItemCopyMatching(query, (CFTypeRef*)cf_result.InitializeInto()); |
| if (status == errSecItemNotFound) { |
| return ""; |
| } |
| if (status != errSecSuccess) { |
| LOG(FATAL) << "Failed to query keychain data. Status: " << status; |
| return ""; |
| } |
| const char* data_pointer = |
| reinterpret_cast<const char*>(CFDataGetBytePtr(cf_result.get())); |
| CFIndex data_size = CFDataGetLength(cf_result.get()); |
| return std::string(data_pointer, data_size); |
| } |
| |
| void RemotingKeychain::RemoveData(Key key, const std::string& account) { |
| std::string service = KeyToService(key); |
| |
| ScopedMutableDictionary query = CreateQueryForUpdate(service, account); |
| OSStatus status = SecItemDelete(query); |
| if (status != errSecSuccess && status != errSecItemNotFound) { |
| LOG(FATAL) << "Failed to delete a keychain item. Status: " << status; |
| } |
| } |
| |
| void RemotingKeychain::SetServicePrefixForTesting( |
| const std::string& service_prefix) { |
| DCHECK(service_prefix.length()); |
| service_prefix_ = service_prefix; |
| } |
| |
| std::string RemotingKeychain::KeyToService(Key key) const { |
| return service_prefix_ + KeyToString(key); |
| } |
| |
| } // namespace remoting |