Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace deprecated SecKeychain API with SecItem #33

Merged
merged 7 commits into from
Aug 26, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -43,3 +43,4 @@ tags

keychain.sublime-workspace
keychain.sublime-project
.vscode
6 changes: 0 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -45,12 +45,6 @@ elseif (APPLE)
PRIVATE
"src/keychain_mac.cpp")

# We're using the deprecated "Legacy Password Storage" API
# (SecKeychainFindGenericPassword and friends)
target_compile_options(${PROJECT_NAME}
PRIVATE
"-Wno-error=deprecated-declarations")

find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED)
find_library(SECURITY_LIBRARY Security REQUIRED)

182 changes: 108 additions & 74 deletions src/keychain_mac.cpp
Original file line number Diff line number Diff line change
@@ -30,9 +30,6 @@
#include "keychain.h"

namespace {

const SecKeychainRef defaultUserKeychain = NULL; // NULL means 'default'

/*! \brief Converts a CFString to a std::string
*
* This either uses CFStringGetCStringPtr or (if that fails) CFStringGetCString.
@@ -104,37 +101,69 @@ void updateError(keychain::Error &err, OSStatus status) {
}
}

/*! \brief Modify an existing password
*
* Helper function that tries to find an existing password in the keychain and
* modifies it.
*/
OSStatus modifyPassword(const std::string &serviceName, const std::string &user,
const std::string &password) {
SecKeychainItemRef item = NULL;
OSStatus status = SecKeychainFindGenericPassword(
defaultUserKeychain,
static_cast<UInt32>(serviceName.length()),
serviceName.data(),
static_cast<UInt32>(user.length()),
user.data(),
NULL, // unused output parameter
NULL, // unused output parameter
&item);
void handleCFCreateFailure(keychain::Error &err,
const std::string &errorMessage) {
err.message = errorMessage;
err.type = keychain::ErrorType::GenericError;
err.code = -1;
}

if (status == errSecSuccess) {
status =
SecKeychainItemModifyContent(item,
NULL,
static_cast<UInt32>(password.length()),
password.data());
CFStringRef createCFStringWithCString(const std::string &str,
keychain::Error &err) {
CFStringRef result = CFStringCreateWithCString(
kCFAllocatorDefault, str.c_str(), kCFStringEncodingUTF8);
if (result == NULL) {
handleCFCreateFailure(err, "Failed to create CFString");
}
return result;
}

CFMutableDictionaryRef createCFMutableDictionary(keychain::Error &err) {
CFMutableDictionaryRef result =
CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (result == NULL) {
handleCFCreateFailure(err, "Failed to create CFMutableDictionary");
}
return result;
}

if (item) {
CFRelease(item);
CFDataRef createCFData(const std::string &data, keychain::Error &err) {
CFDataRef result =
CFDataCreate(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *>(data.c_str()),
data.length());
if (result == NULL) {
handleCFCreateFailure(err, "Failed to create CFData");
}
return result;
}

CFMutableDictionaryRef createQuery(const std::string &serviceName,
const std::string &user,
keychain::Error &err) {
CFStringRef cfServiceName = createCFStringWithCString(serviceName, err);
CFStringRef cfUser = createCFStringWithCString(user, err);
CFMutableDictionaryRef query = createCFMutableDictionary(err);

if (err.type != keychain::ErrorType::NoError) {
if (cfServiceName)
CFRelease(cfServiceName);
if (cfUser)
CFRelease(cfUser);
return NULL;
}

CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(query, kSecAttrAccount, cfUser);
CFDictionaryAddValue(query, kSecAttrService, cfServiceName);

return status;
CFRelease(cfServiceName);
CFRelease(cfUser);

return query;
}

} // namespace
@@ -146,84 +175,89 @@ void setPassword(const std::string &package, const std::string &service,
Error &err) {
err = Error{};
const auto serviceName = makeServiceName(package, service);
CFDataRef cfPassword = createCFData(password, err);
CFMutableDictionaryRef query = createQuery(serviceName, user, err);

if (err.type != keychain::ErrorType::NoError) {
return;
}

OSStatus status =
SecKeychainAddGenericPassword(defaultUserKeychain,
static_cast<UInt32>(serviceName.length()),
serviceName.data(),
static_cast<UInt32>(user.length()),
user.data(),
static_cast<UInt32>(password.length()),
password.data(),
NULL /* unused output parameter */);
CFDictionaryAddValue(query, kSecValueData, cfPassword);

OSStatus status = SecItemAdd(query, NULL);

if (status == errSecDuplicateItem) {
// password exists -- override
status = modifyPassword(serviceName, user, password);
CFMutableDictionaryRef attributesToUpdate =
createCFMutableDictionary(err);
if (err.type != keychain::ErrorType::NoError) {
CFRelease(cfPassword);
CFRelease(query);
return;
}

CFDictionaryAddValue(attributesToUpdate, kSecValueData, cfPassword);
status = SecItemUpdate(query, attributesToUpdate);

CFRelease(attributesToUpdate);
}

if (status != errSecSuccess) {
updateError(err, status);
}

CFRelease(cfPassword);
CFRelease(query);
}

std::string getPassword(const std::string &package, const std::string &service,
const std::string &user, Error &err) {
err = Error{};
std::string password;
const auto serviceName = makeServiceName(package, service);
void *data;
UInt32 length;

OSStatus status = SecKeychainFindGenericPassword(
defaultUserKeychain,
static_cast<UInt32>(serviceName.length()),
serviceName.data(),
static_cast<UInt32>(user.length()),
user.data(),
&length,
&data,
NULL /* unused output parameter */);
CFMutableDictionaryRef query = createQuery(serviceName, user, err);

std::string password;
if (err.type != keychain::ErrorType::NoError) {
return password;
}

CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);

CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(query, &result);

if (status != errSecSuccess) {
updateError(err, status);
} else if (data != NULL) {
password = std::string(reinterpret_cast<const char *>(data), length);
SecKeychainItemFreeContent(NULL, data);
} else if (result != NULL) {
CFDataRef cfPassword = (CFDataRef)result;
password = std::string(
reinterpret_cast<const char *>(CFDataGetBytePtr(cfPassword)),
CFDataGetLength(cfPassword));
CFRelease(result);
}

CFRelease(query);

return password;
}

void deletePassword(const std::string &package, const std::string &service,
const std::string &user, Error &err) {
err = Error{};
const auto serviceName = makeServiceName(package, service);
SecKeychainItemRef item;

OSStatus status = SecKeychainFindGenericPassword(
defaultUserKeychain,
static_cast<UInt32>(serviceName.length()),
serviceName.data(),
static_cast<UInt32>(user.length()),
user.data(),
NULL, // unused output parameter
NULL, // unused output parameter
&item);
CFMutableDictionaryRef query = createQuery(serviceName, user, err);

if (err.type != keychain::ErrorType::NoError) {
return;
}

OSStatus status = SecItemDelete(query);

if (status != errSecSuccess) {
updateError(err, status);
} else {
status = SecKeychainItemDelete(item);
if (status != errSecSuccess) {
updateError(err, status);
}
}

if (!err && item) {
CFRelease(item);
}
CFRelease(query);
}

} // namespace keychain