Skip to content

Commit

Permalink
ScopedCFRef helper to manage CF objects (#36)
Browse files Browse the repository at this point in the history
ScopedCFRef helper to manage CF-Objects
  • Loading branch information
hrantzsch committed Jan 31, 2024
1 parent ec80ca8 commit 673a29b
Showing 1 changed file with 102 additions and 92 deletions.
194 changes: 102 additions & 92 deletions src/keychain_mac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
*
*/

#include <type_traits>
#include <vector>

#include <Security/Security.h>

#include "keychain.h"

namespace {

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

void handleCFCreateFailure(keychain::Error &err,
const std::string &errorMessage) {
void setGenericError(keychain::Error &err, const std::string &errorMessage) {
err = keychain::Error{};
err.message = errorMessage;
err.type = keychain::ErrorType::GenericError;
err.code = -1;
}

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");
/*! \brief Helper to manage the lifetime of CF-Objects
*
* This helper will CFRelease the managed CF-Object when it goes out of scope.
* It assumes ownership of the managed object, so users should own the object in
* terms of the Core Foundation "Create Rule" when passing it to the
* ScopedCFRef. Consequently, the object should also not be released by anyone
* else, at least not without calling CFRetain first.
* */
template <typename T,
typename = typename std::enable_if<std::is_pointer<T>::value>::type>
class ScopedCFRef {
public:
explicit ScopedCFRef(T ref) : _ref(ref) {}
~ScopedCFRef() { _release(); }

ScopedCFRef(ScopedCFRef &&other) noexcept : _ref(other._ref) {
other._ref = nullptr;
}
ScopedCFRef &operator=(ScopedCFRef &&other) {
if (this != &other) {
_release();
_ref = other._ref;
other._ref = nullptr;
}
return *this;
}

ScopedCFRef(const ScopedCFRef &) = delete;
ScopedCFRef &operator=(const ScopedCFRef &) = delete;

const T get() const { return _ref; }
operator bool() const { return _ref != nullptr; }

private:
void _release() {
if (_ref != nullptr) {
CFRelease(_ref);
_ref = nullptr;
}
}

T _ref;
};

ScopedCFRef<CFStringRef> createCFStringWithCString(const std::string &str,
keychain::Error &err) {
auto result = ScopedCFRef<CFStringRef>(CFStringCreateWithCString(
kCFAllocatorDefault, str.c_str(), kCFStringEncodingUTF8));
if (!result)
setGenericError(err, "Failed to create CFString");
return result;
}

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

CFDataRef createCFData(const std::string &data, keychain::Error &err) {
CFDataRef result =
ScopedCFRef<CFDataRef> createCFData(const std::string &data,
keychain::Error &err) {
auto result = ScopedCFRef<CFDataRef>(
CFDataCreate(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *>(data.c_str()),
data.length());
if (result == NULL) {
handleCFCreateFailure(err, "Failed to create CFData");
}
data.length()));
if (!result)
setGenericError(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;
}
ScopedCFRef<CFMutableDictionaryRef> createQuery(const std::string &serviceName,
const std::string &user,
keychain::Error &err) {
const auto cfServiceName = createCFStringWithCString(serviceName, err);
const auto cfUser = createCFStringWithCString(user, err);
auto query = createCFMutableDictionary(err);

CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(query, kSecAttrAccount, cfUser);
CFDictionaryAddValue(query, kSecAttrService, cfServiceName);
if (err.type != keychain::ErrorType::NoError)
return query;

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

return query;
}
Expand All @@ -175,89 +212,62 @@ 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);
const auto cfPassword = createCFData(password, err);
auto query = createQuery(serviceName, user, err);

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

CFDictionaryAddValue(query, kSecValueData, cfPassword);

OSStatus status = SecItemAdd(query, NULL);
CFDictionaryAddValue(query.get(), kSecValueData, cfPassword.get());
OSStatus status = SecItemAdd(query.get(), NULL);

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

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

CFRelease(attributesToUpdate);
CFDictionaryAddValue(
attributesToUpdate.get(), kSecValueData, cfPassword.get());
status = SecItemUpdate(query.get(), attributesToUpdate.get());
}

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

CFRelease(cfPassword);
CFRelease(query);
updateError(err, status);
}

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);
CFMutableDictionaryRef query = createQuery(serviceName, user, err);
auto query = createQuery(serviceName, user, err);

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

CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
CFDictionaryAddValue(query.get(), kSecReturnData, kCFBooleanTrue);

CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(query, &result);
CFTypeRef result = nullptr;
updateError(err, SecItemCopyMatching(query.get(), &result));
const auto cfPassword = ScopedCFRef<CFDataRef>((CFDataRef)result);

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

CFRelease(query);
if (!cfPassword || err.type != keychain::ErrorType::NoError)
return "";

return password;
return std::string(
reinterpret_cast<const char *>(CFDataGetBytePtr(cfPassword.get())),
CFDataGetLength(cfPassword.get()));
}

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

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

OSStatus status = SecItemDelete(query);

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

CFRelease(query);
updateError(err, SecItemDelete(query.get()));
}

} // namespace keychain

0 comments on commit 673a29b

Please sign in to comment.