aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/qtkeychain/keychain_apple.mm
blob: f9a8266be0538682898e918b8921863739a09a3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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;
}