aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/qtkeychain/keychain_apple.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/3rdparty/qtkeychain/keychain_apple.mm')
-rw-r--r--src/libs/3rdparty/qtkeychain/keychain_apple.mm263
1 files changed, 263 insertions, 0 deletions
diff --git a/src/libs/3rdparty/qtkeychain/keychain_apple.mm b/src/libs/3rdparty/qtkeychain/keychain_apple.mm
new file mode 100644
index 0000000000..f9a8266be0
--- /dev/null
+++ b/src/libs/3rdparty/qtkeychain/keychain_apple.mm
@@ -0,0 +1,263 @@
+/******************************************************************************
+ * Copyright (C) 2016 Mathias Hasselmann <mathias.hasselmann@kdab.com> *
+ * *
+ * This program is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
+ * or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
+ * details, check the accompanying file 'COPYING'. *
+ *****************************************************************************/
+
+#include "keychain_p.h"
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+using namespace QKeychain;
+
+struct ErrorDescription
+{
+ QKeychain::Error code;
+ QString message;
+
+ ErrorDescription(QKeychain::Error code, const QString &message)
+ : code(code), message(message) {}
+
+ static ErrorDescription fromStatus(OSStatus status)
+ {
+ switch(status) {
+ case errSecSuccess:
+ return ErrorDescription(QKeychain::NoError, Job::tr("No error"));
+ case errSecItemNotFound:
+ return ErrorDescription(QKeychain::EntryNotFound, Job::tr("The specified item could not be found in the keychain"));
+ case errSecUserCanceled:
+ return ErrorDescription(QKeychain::AccessDeniedByUser, Job::tr("User canceled the operation"));
+ case errSecInteractionNotAllowed:
+ return ErrorDescription(QKeychain::AccessDenied, Job::tr("User interaction is not allowed"));
+ case errSecNotAvailable:
+ return ErrorDescription(QKeychain::AccessDenied, Job::tr("No keychain is available. You may need to restart your computer"));
+ case errSecAuthFailed:
+ return ErrorDescription(QKeychain::AccessDenied, Job::tr("The user name or passphrase you entered is not correct"));
+ case errSecVerifyFailed:
+ return ErrorDescription(QKeychain::AccessDenied, Job::tr("A cryptographic verification failure has occurred"));
+ case errSecUnimplemented:
+ return ErrorDescription(QKeychain::NotImplemented, Job::tr("Function or operation not implemented"));
+ case errSecIO:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("I/O error"));
+ case errSecOpWr:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Already open with with write permission"));
+ case errSecParam:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Invalid parameters passed to a function"));
+ case errSecAllocate:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Failed to allocate memory"));
+ case errSecBadReq:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Bad parameter or invalid state for operation"));
+ case errSecInternalComponent:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("An internal component failed"));
+ case errSecDuplicateItem:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("The specified item already exists in the keychain"));
+ case errSecDecode:
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Unable to decode the provided data"));
+ }
+
+ return ErrorDescription(QKeychain::OtherError, Job::tr("Unknown error"));
+ }
+};
+
+@interface AppleKeychainInterface : NSObject
+
+- (instancetype)initWithJob:(Job *)job andPrivateJob:(JobPrivate *)privateJob;
+- (void)keychainTaskFinished;
+- (void)keychainReadTaskFinished:(NSData *)retrievedData;
+- (void)keychainTaskFinishedWithError:(OSStatus)status descriptiveMessage:(NSString *)descriptiveMessage;
+
+@end
+
+@interface AppleKeychainInterface()
+{
+ QPointer<Job> _job;
+ QPointer<JobPrivate> _privateJob;
+}
+@end
+
+@implementation AppleKeychainInterface
+
+- (instancetype)initWithJob:(Job *)job andPrivateJob:(JobPrivate *)privateJob
+{
+ self = [super init];
+ if (self) {
+ _job = job;
+ _privateJob = privateJob;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [NSNotificationCenter.defaultCenter removeObserver:self];
+ [super dealloc];
+}
+
+- (void)keychainTaskFinished
+{
+ if (_job) {
+ _job->emitFinished();
+ }
+}
+
+- (void)keychainReadTaskFinished:(NSData *)retrievedData
+{
+ _privateJob->data.clear();
+ _privateJob->mode = JobPrivate::Binary;
+
+ if (retrievedData != nil) {
+ if (_privateJob) {
+ _privateJob->data = QByteArray::fromNSData(retrievedData);
+ }
+ }
+
+ if (_job) {
+ _job->emitFinished();
+ }
+}
+
+- (void)keychainTaskFinishedWithError:(OSStatus)status descriptiveMessage:(NSString *)descriptiveMessage
+{
+ const auto localisedDescriptiveMessage = Job::tr([descriptiveMessage UTF8String]);
+
+ const ErrorDescription error = ErrorDescription::fromStatus(status);
+ const auto fullMessage = localisedDescriptiveMessage.isEmpty() ? error.message : QStringLiteral("%1: %2").arg(localisedDescriptiveMessage, error.message);
+
+ if (_job) {
+ _job->emitFinishedWithError(error.code, fullMessage);
+ }
+}
+
+@end
+
+
+static void StartReadPassword(const QString &service, const QString &key, AppleKeychainInterface * const interface)
+{
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+ NSDictionary * const query = @{
+ (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
+ (__bridge NSString *)kSecAttrService: service.toNSString(),
+ (__bridge NSString *)kSecAttrAccount: key.toNSString(),
+ (__bridge NSString *)kSecReturnData: @YES,
+ };
+
+ CFTypeRef dataRef = nil;
+ const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef);
+
+ if (status == errSecSuccess) {
+ const CFDataRef castedDataRef = (CFDataRef)dataRef;
+ NSData * const data = (__bridge NSData *)castedDataRef;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainReadTaskFinished:data];
+ [interface release];
+ });
+ } else {
+ NSString * const descriptiveErrorString = @"Could not retrieve private key from keystore";
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
+ [interface release];
+ });
+ }
+
+ if (dataRef) {
+ CFRelease(dataRef);
+ }
+ });
+}
+
+static void StartWritePassword(const QString &service, const QString &key, const QByteArray &data, AppleKeychainInterface * const interface)
+{
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+ NSDictionary * const query = @{
+ (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
+ (__bridge NSString *)kSecAttrService: service.toNSString(),
+ (__bridge NSString *)kSecAttrAccount: key.toNSString(),
+ };
+
+ OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, nil);
+
+ if (status == errSecSuccess) {
+ NSDictionary * const update = @{
+ (__bridge NSString *)kSecValueData: data.toNSData(),
+ };
+
+ status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
+ } else {
+ NSDictionary * const insert = @{
+ (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
+ (__bridge NSString *)kSecAttrService: service.toNSString(),
+ (__bridge NSString *)kSecAttrAccount: key.toNSString(),
+ (__bridge NSString *)kSecValueData: data.toNSData(),
+ };
+
+ status = SecItemAdd((__bridge const CFDictionaryRef)insert, nil);
+ }
+
+ if (status == errSecSuccess) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainTaskFinished];
+ [interface release];
+ });
+ } else {
+ NSString * const descriptiveErrorString = @"Could not store data in settings";
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
+ [interface release];
+ });
+ }
+ });
+}
+
+static void StartDeletePassword(const QString &service, const QString &key, AppleKeychainInterface * const interface)
+{
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+ NSDictionary * const query = @{
+ (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
+ (__bridge NSString *)kSecAttrService: service.toNSString(),
+ (__bridge NSString *)kSecAttrAccount: key.toNSString(),
+ };
+
+ const OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+
+ if (status == errSecSuccess) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainTaskFinished];
+ [interface release];
+ });
+ } else {
+ NSString * const descriptiveErrorString = @"Could not remove private key from keystore";
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
+ [interface release];
+ });
+ }
+ });
+}
+
+void ReadPasswordJobPrivate::scheduledStart()
+{
+ AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
+ StartReadPassword(service, key, interface);
+}
+
+void WritePasswordJobPrivate::scheduledStart()
+{
+ AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
+ StartWritePassword(service, key, data, interface);
+}
+
+void DeletePasswordJobPrivate::scheduledStart()
+{
+ AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
+ StartDeletePassword(service, key, interface);
+}
+
+bool QKeychain::isAvailable()
+{
+ return true;
+}