1 /*****************************************************************************
2 * keychain.m: Darwin Keychain keystore module
3 *****************************************************************************
4 * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
6 * Author: Felix Paul Kühne <fkuehne # videolabs.io>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_keystore.h>
31 #import <Foundation/Foundation.h>
32 #import <Security/Security.h>
33 #import <Cocoa/Cocoa.h>
35 static int Open(vlc_object_t *);
37 static const int sync_list[] =
39 static const char *const sync_list_text[] = {
40 N_("Yes"), N_("No"), N_("Any")
43 static const int accessibility_list[] =
44 { 0, 1, 2, 3, 4, 5, 6, 7 };
45 static const char *const accessibility_list_text[] = {
47 N_("After first unlock"),
48 N_("After first unlock, on this device only"),
50 N_("When passcode set, on this device only"),
51 N_("Always, on this device only"),
53 N_("When unlocked, on this device only")
56 #define SYNC_ITEMS_TEXT N_("Synchronize stored items")
57 #define SYNC_ITEMS_LONGTEXT N_("Synchronizes stored items via iCloud Keychain if enabled in the user domain. Requires iOS 7 / Mac OS X 10.9 / tvOS 9.0 or higher.")
59 #define ACCESSIBILITY_TYPE_TEXT N_("Accessibility type for all future passwords saved to the Keychain")
61 #define ACCESS_GROUP_TEXT N_("Keychain access group")
62 #define ACCESS_GROUP_LONGTEXT N_("Keychain access group as defined by the app entitlements. Requires iOS 3 / Mac OS X 10.9 / tvOS 9.0 or higher.")
64 /* VLC can be compiled against older SDKs (like before OS X 10.10)
65 * but newer features should still be available.
66 * Hence, re-define things as needed */
67 #ifndef kSecAttrSynchronizable
68 #define kSecAttrSynchronizable CFSTR("sync")
71 #ifndef kSecAttrSynchronizableAny
72 #define kSecAttrSynchronizableAny CFSTR("syna")
75 #ifndef kSecAttrAccessGroup
76 #define kSecAttrAccessGroup CFSTR("agrp")
79 #ifndef kSecAttrAccessibleAfterFirstUnlock
80 #define kSecAttrAccessibleAfterFirstUnlock CFSTR("ck")
83 #ifndef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
84 #define kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly CFSTR("cku")
87 #ifndef kSecAttrAccessibleAlways
88 #define kSecAttrAccessibleAlways CFSTR("dk")
91 #ifndef kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
92 #define kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly CFSTR("akpu")
95 #ifndef kSecAttrAccessibleAlwaysThisDeviceOnly
96 #define kSecAttrAccessibleAlwaysThisDeviceOnly CFSTR("dku")
99 #ifndef kSecAttrAccessibleWhenUnlocked
100 #define kSecAttrAccessibleWhenUnlocked CFSTR("ak")
103 #ifndef kSecAttrAccessibleWhenUnlockedThisDeviceOnly
104 #define kSecAttrAccessibleWhenUnlockedThisDeviceOnly CFSTR("aku")
108 set_shortname(N_("Keychain keystore"))
109 set_description(N_("Keystore for iOS, Mac OS X and tvOS"))
110 set_category(CAT_ADVANCED)
111 set_subcategory(SUBCAT_ADVANCED_MISC)
112 add_integer("keychain-synchronize", 1, SYNC_ITEMS_TEXT, SYNC_ITEMS_LONGTEXT, true)
113 change_integer_list(sync_list, sync_list_text)
114 add_integer("keychain-accessibility-type", 0, ACCESSIBILITY_TYPE_TEXT, ACCESSIBILITY_TYPE_TEXT, true)
115 change_integer_list(accessibility_list, accessibility_list_text)
116 add_string("keychain-access-group", NULL, ACCESS_GROUP_TEXT, ACCESS_GROUP_LONGTEXT, true)
117 set_capability("keystore", 100)
118 set_callbacks(Open, NULL)
121 static NSMutableDictionary * CreateQuery(vlc_keystore *p_keystore)
123 NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
124 [dictionary setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
126 [dictionary setObject:@"VLC-Password-Service" forKey:(__bridge id)kSecAttrService];
128 const char * psz_access_group = var_InheritString(p_keystore, "keychain-access-group");
129 if (psz_access_group) {
130 [dictionary setObject:[NSString stringWithUTF8String:psz_access_group] forKey:(__bridge id)kSecAttrAccessGroup];
134 int syncMode = var_InheritInteger(p_keystore, "keychain-synchronize");
137 syncValue = (__bridge id)kSecAttrSynchronizableAny;
138 } else if (syncMode == 0) {
144 [dictionary setObject:syncValue forKey:(__bridge id)(kSecAttrSynchronizable)];
149 static NSString * ErrorForStatus(OSStatus status)
151 NSString *message = nil;
155 case errSecUnimplemented: {
156 message = @"Query unimplemented";
160 message = @"Faulty parameter";
163 case errSecAllocate: {
164 message = @"Allocation failure";
167 case errSecNotAvailable: {
168 message = @"Query not available";
171 case errSecDuplicateItem: {
172 message = @"Duplicated item";
175 case errSecItemNotFound: {
176 message = @"Item not found";
179 case errSecInteractionNotAllowed: {
180 message = @"Interaction not allowed";
184 message = @"Decoding failure";
187 case errSecAuthFailed: {
188 message = @"Authentication failure";
192 message = @"iCloud Keychain failure";
196 message = @"Unknown generic error";
200 message = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
207 #define OSX_MAVERICKS (NSAppKitVersionNumber >= 1265)
208 extern const CFStringRef kSecAttrAccessible;
210 static void SetAccessibilityForQuery(vlc_keystore *p_keystore,
211 NSMutableDictionary *query)
216 int accessibilityType = var_InheritInteger(p_keystore, "keychain-accessibility-type");
217 switch (accessibilityType) {
219 [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible];
222 [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
225 [query setObject:(__bridge id)kSecAttrAccessibleAlways forKey:(__bridge id)kSecAttrAccessible];
228 [query setObject:(__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
231 [query setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
234 [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
237 [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
244 static void SetAttributesForQuery(const char *const ppsz_values[KEY_MAX], NSMutableDictionary *query, const char *psz_label)
246 const char *psz_protocol = ppsz_values[KEY_PROTOCOL];
247 const char *psz_user = ppsz_values[KEY_USER];
248 const char *psz_server = ppsz_values[KEY_SERVER];
249 const char *psz_path = ppsz_values[KEY_PATH];
250 const char *psz_port = ppsz_values[KEY_PORT];
253 [query setObject:[NSString stringWithUTF8String:psz_label] forKey:(__bridge id)kSecAttrLabel];
256 [query setObject:[NSString stringWithUTF8String:psz_protocol] forKey:(__bridge id)kSecAttrProtocol];
259 [query setObject:[NSString stringWithUTF8String:psz_user] forKey:(__bridge id)kSecAttrAccount];
262 [query setObject:[NSString stringWithUTF8String:psz_server] forKey:(__bridge id)kSecAttrServer];
265 [query setObject:[NSString stringWithUTF8String:psz_path] forKey:(__bridge id)kSecAttrPath];
268 [query setObject:[NSString stringWithUTF8String:psz_port] forKey:(__bridge id)kSecAttrPort];
272 static int CopyEntryValues(const char * ppsz_dst[KEY_MAX], const char *const ppsz_src[KEY_MAX])
274 for (unsigned int i = 0; i < KEY_MAX; ++i)
278 ppsz_dst[i] = strdup(ppsz_src[i]);
286 static int Store(vlc_keystore *p_keystore,
287 const char *const ppsz_values[KEY_MAX],
288 const uint8_t *p_secret,
290 const char *psz_label)
294 if (!ppsz_values[KEY_PROTOCOL] || !p_secret) {
298 NSMutableDictionary *query = nil;
299 NSMutableDictionary *searchQuery = CreateQuery(p_keystore);
302 SetAttributesForQuery(ppsz_values, searchQuery, psz_label);
305 status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, nil);
307 /* create storage unit */
308 NSData *secretData = [[NSString stringWithFormat:@"%s", p_secret] dataUsingEncoding:NSUTF8StringEncoding];
310 if (status == errSecSuccess) {
311 /* item already existed in keychain, let's update */
312 query = [[NSMutableDictionary alloc] init];
314 /* just set the secret data */
315 [query setObject:secretData forKey:(__bridge id)kSecValueData];
317 status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
318 } else if (status == errSecItemNotFound) {
319 /* item not found, let's create! */
320 query = CreateQuery(p_keystore);
323 SetAttributesForQuery(ppsz_values, query, psz_label);
325 /* set accessibility */
326 SetAccessibilityForQuery(p_keystore, query);
328 /* set secret data */
329 [query setObject:secretData forKey:(__bridge id)kSecValueData];
331 status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
333 if (status != errSecSuccess) {
334 msg_Err(p_keystore, "Storage failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
341 static unsigned int Find(vlc_keystore *p_keystore,
342 const char *const ppsz_values[KEY_MAX],
343 vlc_keystore_entry **pp_entries)
345 CFTypeRef result = NULL;
347 NSMutableDictionary *query = CreateQuery(p_keystore);
348 [query setObject:@(YES) forKey:(__bridge id)kSecReturnRef];
349 [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
352 SetAttributesForQuery(ppsz_values, query, NULL);
355 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
357 if (status != errSecSuccess) {
358 msg_Warn(p_keystore, "lookup failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
362 NSArray *listOfResults = (__bridge_transfer NSArray *)result;
364 NSUInteger count = listOfResults.count;
366 vlc_keystore_entry *p_entries = calloc(count,
367 sizeof(vlc_keystore_entry));
371 for (NSUInteger i = 0; i < count; i++) {
372 vlc_keystore_entry *p_entry = &p_entries[i];
373 if (CopyEntryValues((const char **)p_entry->ppsz_values, (const char *const*)ppsz_values) != VLC_SUCCESS) {
374 vlc_keystore_release_entries(p_entries, 1);
378 SecKeychainItemRef itemRef = (__bridge SecKeychainItemRef)([listOfResults objectAtIndex:i]);
380 SecKeychainAttributeInfo attrInfo;
384 UInt32 tags[1] = {kSecAccountItemAttr}; //, kSecAccountItemAttr, kSecServerItemAttr, kSecPortItemAttr, kSecProtocolItemAttr, kSecPathItemAttr};
386 attrInfo.format = NULL;
389 SecKeychainAttributeList *attrList = NULL;
394 status = SecKeychainItemCopyAttributesAndData(itemRef, &attrInfo, NULL, &attrList, &dataLength, &data);
396 if (status != noErr) {
397 msg_Err(p_keystore, "Lookup error: %i (%s)", status, [ErrorForStatus(status) UTF8String]);
398 vlc_keystore_release_entries(p_entries, count);
403 for (unsigned x = 0; x < attrList->count; x++) {
404 SecKeychainAttribute *attr = &attrList->attr[i];
406 case kSecLabelItemAttr:
407 NSLog(@"label %@", [[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding]);
409 case kSecAccountItemAttr:
410 NSLog(@"account %@", [[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding]);
418 /* we need to do some padding here, as string is expected to be 0 terminated */
419 uint8_t *retData = calloc(1, dataLength + 1);
420 memcpy(retData, data, dataLength);
422 vlc_keystore_entry_set_secret(p_entry, retData, dataLength + 1);
425 SecKeychainItemFreeAttributesAndData(attrList, data);
428 *pp_entries = p_entries;
433 static unsigned int Remove(vlc_keystore *p_keystore,
434 const char *const ppsz_values[KEY_MAX])
438 NSMutableDictionary *query = CreateQuery(p_keystore);
440 SetAttributesForQuery(ppsz_values, query, NULL);
442 CFTypeRef result = NULL;
443 [query setObject:@(YES) forKey:(__bridge id)kSecReturnRef];
444 [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
447 status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
449 NSUInteger matchCount = 0;
451 if (status == errSecSuccess) {
452 NSArray *matches = (__bridge_transfer NSArray *)result;
453 matchCount = matches.count;
455 for (NSUInteger x = 0; x < matchCount; x++) {
456 status = SecKeychainItemDelete((__bridge SecKeychainItemRef _Nonnull)([matches objectAtIndex:x]));
457 if (status != noErr) {
458 msg_Err(p_keystore, "Deletion error %i (%s)", status , [ErrorForStatus(status) UTF8String]);
463 msg_Err(p_keystore, "Lookup error for deletion %i (%s)", status, [ErrorForStatus(status) UTF8String]);
473 static int Open(vlc_object_t *p_this)
475 vlc_keystore *p_keystore = (vlc_keystore *)p_this;
477 p_keystore->p_sys = NULL;
478 p_keystore->pf_store = Store;
479 p_keystore->pf_find = Find;
480 p_keystore->pf_remove = Remove;