diff --git a/src/keychain_mac.cpp b/src/keychain_mac.cpp index 76b51a3..1b84113 100644 --- a/src/keychain_mac.cpp +++ b/src/keychain_mac.cpp @@ -23,6 +23,7 @@ * */ +#include #include #include @@ -30,6 +31,7 @@ #include "keychain.h" namespace { + /*! \brief Converts a CFString to a std::string * * This either uses CFStringGetCStringPtr or (if that fails) CFStringGetCString. @@ -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 ::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 createCFStringWithCString(const std::string &str, + keychain::Error &err) { + auto result = ScopedCFRef(CFStringCreateWithCString( + kCFAllocatorDefault, str.c_str(), kCFStringEncodingUTF8)); + if (!result) + setGenericError(err, "Failed to create CFString"); return result; } -CFMutableDictionaryRef createCFMutableDictionary(keychain::Error &err) { - CFMutableDictionaryRef result = +ScopedCFRef +createCFMutableDictionary(keychain::Error &err) { + auto result = ScopedCFRef( 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 createCFData(const std::string &data, + keychain::Error &err) { + auto result = ScopedCFRef( CFDataCreate(kCFAllocatorDefault, reinterpret_cast(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 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; } @@ -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)result); - if (status != errSecSuccess) { - updateError(err, status); - } else if (result != NULL) { - CFDataRef cfPassword = (CFDataRef)result; - password = std::string( - reinterpret_cast(CFDataGetBytePtr(cfPassword)), - CFDataGetLength(cfPassword)); - CFRelease(result); - } - - CFRelease(query); + if (!cfPassword || err.type != keychain::ErrorType::NoError) + return ""; - return password; + return std::string( + reinterpret_cast(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