demux: libmp4: add and parse 3DDS uuid
[vlc.git] / modules / keystore / keychain.m
blob8d05ee31a57b8f9b18bb883d4f018b5d2daaeea6
1 /*****************************************************************************
2  * keychain.m: Darwin Keychain keystore module
3  *****************************************************************************
4  * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * Author: Felix Paul Kühne <fkuehne # videolabs.io>
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
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[] =
38 { 0, 1, 2 };
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[] = {
46     N_("System default"),
47     N_("After first unlock"),
48     N_("After first unlock, on this device only"),
49     N_("Always"),
50     N_("When passcode set, on this device only"),
51     N_("Always, on this device only"),
52     N_("When unlocked"),
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")
69 #endif
71 #ifndef kSecAttrSynchronizableAny
72 #define kSecAttrSynchronizableAny CFSTR("syna")
73 #endif
75 #ifndef kSecAttrAccessGroup
76 #define kSecAttrAccessGroup CFSTR("agrp")
77 #endif
79 #ifndef kSecAttrAccessibleAfterFirstUnlock
80 #define kSecAttrAccessibleAfterFirstUnlock CFSTR("ck")
81 #endif
83 #ifndef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
84 #define kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly CFSTR("cku")
85 #endif
87 #ifndef kSecAttrAccessibleAlways
88 #define kSecAttrAccessibleAlways CFSTR("dk")
89 #endif
91 #ifndef kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
92 #define kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly CFSTR("akpu")
93 #endif
95 #ifndef kSecAttrAccessibleAlwaysThisDeviceOnly
96 #define kSecAttrAccessibleAlwaysThisDeviceOnly CFSTR("dku")
97 #endif
99 #ifndef kSecAttrAccessibleWhenUnlocked
100 #define kSecAttrAccessibleWhenUnlocked CFSTR("ak")
101 #endif
103 #ifndef kSecAttrAccessibleWhenUnlockedThisDeviceOnly
104 #define kSecAttrAccessibleWhenUnlockedThisDeviceOnly CFSTR("aku")
105 #endif
107 vlc_module_begin()
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)
119 vlc_module_end ()
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];
131     }
133     id syncValue;
134     int syncMode = var_InheritInteger(p_keystore, "keychain-synchronize");
136     if (syncMode == 2) {
137         syncValue = (__bridge id)kSecAttrSynchronizableAny;
138     } else if (syncMode == 0) {
139         syncValue = @(YES);
140     } else {
141         syncValue = @(NO);
142     }
144     [dictionary setObject:syncValue forKey:(__bridge id)(kSecAttrSynchronizable)];
146     return dictionary;
149 static NSString * ErrorForStatus(OSStatus status)
151     NSString *message = nil;
153     switch (status) {
154 #if TARGET_OS_IPHONE
155         case errSecUnimplemented: {
156             message = @"Query unimplemented";
157             break;
158         }
159         case errSecParam: {
160             message = @"Faulty parameter";
161             break;
162         }
163         case errSecAllocate: {
164             message = @"Allocation failure";
165             break;
166         }
167         case errSecNotAvailable: {
168             message = @"Query not available";
169             break;
170         }
171         case errSecDuplicateItem: {
172             message = @"Duplicated item";
173             break;
174         }
175         case errSecItemNotFound: {
176             message = @"Item not found";
177             break;
178         }
179         case errSecInteractionNotAllowed: {
180             message = @"Interaction not allowed";
181             break;
182         }
183         case errSecDecode: {
184             message = @"Decoding failure";
185             break;
186         }
187         case errSecAuthFailed: {
188             message = @"Authentication failure";
189             break;
190         }
191         case -34018: {
192             message = @"iCloud Keychain failure";
193             break;
194         }
195         default: {
196             message = @"Unknown generic error";
197         }
198 #else
199         default:
200             message = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
201 #endif
202     }
204     return message;
207 #define OSX_MAVERICKS (NSAppKitVersionNumber >= 1265)
208 extern const CFStringRef kSecAttrAccessible;
210 static void SetAccessibilityForQuery(vlc_keystore *p_keystore,
211                                      NSMutableDictionary *query)
213     if (!OSX_MAVERICKS)
214         return;
216     int accessibilityType = var_InheritInteger(p_keystore, "keychain-accessibility-type");
217     switch (accessibilityType) {
218         case 1:
219             [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible];
220             break;
221         case 2:
222             [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
223             break;
224         case 3:
225             [query setObject:(__bridge id)kSecAttrAccessibleAlways forKey:(__bridge id)kSecAttrAccessible];
226             break;
227         case 4:
228             [query setObject:(__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
229             break;
230         case 5:
231             [query setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
232             break;
233         case 6:
234             [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
235             break;
236         case 7:
237             [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
238             break;
239         default:
240             break;
241     }
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];
252     if (psz_label) {
253         [query setObject:[NSString stringWithUTF8String:psz_label] forKey:(__bridge id)kSecAttrLabel];
254     }
255     if (psz_protocol) {
256         [query setObject:[NSString stringWithUTF8String:psz_protocol] forKey:(__bridge id)kSecAttrProtocol];
257     }
258     if (psz_user) {
259         [query setObject:[NSString stringWithUTF8String:psz_user] forKey:(__bridge id)kSecAttrAccount];
260     }
261     if (psz_server) {
262         [query setObject:[NSString stringWithUTF8String:psz_server] forKey:(__bridge id)kSecAttrServer];
263     }
264     if (psz_path) {
265         [query setObject:[NSString stringWithUTF8String:psz_path] forKey:(__bridge id)kSecAttrPath];
266     }
267     if (psz_port) {
268         [query setObject:[NSString stringWithUTF8String:psz_port] forKey:(__bridge id)kSecAttrPort];
269     }
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)
275     {
276         if (ppsz_src[i])
277         {
278             ppsz_dst[i] = strdup(ppsz_src[i]);
279             if (!ppsz_dst[i])
280                 return VLC_EGENERIC;
281         }
282     }
283     return VLC_SUCCESS;
286 static int Store(vlc_keystore *p_keystore,
287                  const char *const ppsz_values[KEY_MAX],
288                  const uint8_t *p_secret,
289                  size_t i_secret_len,
290                  const char *psz_label)
292     OSStatus status;
294     if (!ppsz_values[KEY_PROTOCOL] || !p_secret) {
295         return VLC_EGENERIC;
296     }
298     NSMutableDictionary *query = nil;
299     NSMutableDictionary *searchQuery = CreateQuery(p_keystore);
301     /* set attributes */
302     SetAttributesForQuery(ppsz_values, searchQuery, psz_label);
304     /* search */
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);
322         /* set attributes */
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);
332     }
333     if (status != errSecSuccess) {
334         msg_Err(p_keystore, "Storage failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
335         return VLC_EGENERIC;
336     }
338     return VLC_SUCCESS;
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];
351     /* set attributes */
352     SetAttributesForQuery(ppsz_values, query, NULL);
354     /* search */
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]);
359         return 0;
360     }
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));
368     if (!p_entries)
369         return 0;
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);
375             return 0;
376         }
378         SecKeychainItemRef itemRef = (__bridge SecKeychainItemRef)([listOfResults objectAtIndex:i]);
380         SecKeychainAttributeInfo attrInfo;
382 #ifndef NDEBUG
383         attrInfo.count = 1;
384         UInt32 tags[1] = {kSecAccountItemAttr}; //, kSecAccountItemAttr, kSecServerItemAttr, kSecPortItemAttr, kSecProtocolItemAttr, kSecPathItemAttr};
385         attrInfo.tag = tags;
386         attrInfo.format = NULL;
387 #endif
389         SecKeychainAttributeList *attrList = NULL;
391         UInt32 dataLength;
392         void * data;
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);
399             return 0;
400         }
402 #ifndef NDEBUG
403         for (unsigned x = 0; x < attrList->count; x++) {
404             SecKeychainAttribute *attr = &attrList->attr[i];
405             switch (attr->tag) {
406                 case kSecLabelItemAttr:
407                     NSLog(@"label %@", [[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding]);
408                     break;
409                 case kSecAccountItemAttr:
410                     NSLog(@"account %@", [[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding]);
411                     break;
412                 default:
413                     break;
414             }
415         }
416 #endif
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);
424         free(retData);
425         SecKeychainItemFreeAttributesAndData(attrList, data);
426     }
428     *pp_entries = p_entries;
430     return count;
433 static unsigned int Remove(vlc_keystore *p_keystore,
434                            const char *const ppsz_values[KEY_MAX])
436     OSStatus status;
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];
446     BOOL failed = NO;
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]);
459                 failed = YES;
460             }
461         }
462     } else {
463         msg_Err(p_keystore, "Lookup error for deletion %i (%s)", status, [ErrorForStatus(status) UTF8String]);
464         return VLC_EGENERIC;
465     }
467     if (failed)
468         return VLC_EGENERIC;
470     return matchCount;
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;
482     return VLC_SUCCESS;