demux: mp4: save track start point
[vlc.git] / modules / keystore / keychain.m
blobf5ccf36b636447c7e5c1c0559f671bf4b6831464
1 /*****************************************************************************
2  * keychain.m: Darwin Keychain keystore module
3  *****************************************************************************
4  * Copyright © 2016, 2018 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 #include "list_util.h"
33 #include <TargetConditionals.h>
35 #import <Foundation/Foundation.h>
36 #import <Security/Security.h>
38 // Marker to recognize changed format in vlc 4: secret does not have \0 cut off anymore.
39 int kVlc4Creator = 'vlc4';
41 static int Open(vlc_object_t *);
43 static const int sync_list[] =
44 { 0, 1, 2 };
45 static const char *const sync_list_text[] = {
46     N_("Yes"), N_("No"), N_("Any")
49 static const int accessibility_list[] =
50 { 0, 1, 2, 3, 4, 5, 6, 7 };
51 static const char *const accessibility_list_text[] = {
52     N_("System default"),
53     N_("After first unlock"),
54     N_("After first unlock, on this device only"),
55     N_("Always"),
56     N_("When passcode set, on this device only"),
57     N_("Always, on this device only"),
58     N_("When unlocked"),
59     N_("When unlocked, on this device only")
62 #define SYNC_ITEMS_TEXT N_("Synchronize stored items")
63 #define SYNC_ITEMS_LONGTEXT N_("Synchronizes stored items via iCloud Keychain if enabled in the user domain.")
65 #define ACCESSIBILITY_TYPE_TEXT N_("Accessibility type for all future passwords saved to the Keychain")
67 #define ACCESS_GROUP_TEXT N_("Keychain access group")
68 #define ACCESS_GROUP_LONGTEXT N_("Keychain access group as defined by the app entitlements.")
71 vlc_module_begin()
72     set_shortname(N_("Keychain keystore"))
73     set_description(N_("Keystore for iOS, macOS and tvOS"))
74     set_category(CAT_ADVANCED)
75     set_subcategory(SUBCAT_ADVANCED_MISC)
76     add_integer("keychain-synchronize", 1, SYNC_ITEMS_TEXT, SYNC_ITEMS_LONGTEXT, true)
77     change_integer_list(sync_list, sync_list_text)
78     add_integer("keychain-accessibility-type", 0, ACCESSIBILITY_TYPE_TEXT, ACCESSIBILITY_TYPE_TEXT, true)
79     change_integer_list(accessibility_list, accessibility_list_text)
80     add_string("keychain-access-group", NULL, ACCESS_GROUP_TEXT, ACCESS_GROUP_LONGTEXT, true)
81     set_capability("keystore", 100)
82     set_callback(Open)
83 vlc_module_end ()
85 static NSMutableDictionary * CreateQuery(vlc_keystore *p_keystore)
87     NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
88     [dictionary setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
90     /* kSecAttrService is only valid for kSecClassGenericPassword but not for
91      * kSecClassInternetPassword. Nevertheless, it is used on macOS for the
92      * wrong password type. It has always worked for now, and changing it would
93      * need to handle a password migration. Using this attribute on iOS cause a
94      * errSecNoSuchAttr error. */
95 #if !TARGET_OS_IPHONE
96     [dictionary setObject:@"VLC-Password-Service" forKey:(__bridge id)kSecAttrService];
97 #endif
99     char * psz_access_group = var_InheritString(p_keystore, "keychain-access-group");
100     if (psz_access_group) {
101         [dictionary setObject:[NSString stringWithUTF8String:psz_access_group] forKey:(__bridge id)kSecAttrAccessGroup];
102     }
103     free(psz_access_group);
105     id syncValue;
106     int syncMode = var_InheritInteger(p_keystore, "keychain-synchronize");
108     if (syncMode == 2) {
109         syncValue = (__bridge id)kSecAttrSynchronizableAny;
110     } else if (syncMode == 0) {
111         syncValue = @(YES);
112     } else {
113         syncValue = @(NO);
114     }
116     [dictionary setObject:syncValue forKey:(__bridge id)(kSecAttrSynchronizable)];
118     return dictionary;
121 static NSString * ErrorForStatus(OSStatus status)
123     NSString *message = nil;
125     switch (status) {
126 #if TARGET_OS_IPHONE
127         case errSecUnimplemented: {
128             message = @"Query unimplemented";
129             break;
130         }
131         case errSecParam: {
132             message = @"Faulty parameter";
133             break;
134         }
135         case errSecAllocate: {
136             message = @"Allocation failure";
137             break;
138         }
139         case errSecNotAvailable: {
140             message = @"Query not available";
141             break;
142         }
143         case errSecDuplicateItem: {
144             message = @"Duplicated item";
145             break;
146         }
147         case errSecItemNotFound: {
148             message = @"Item not found";
149             break;
150         }
151         case errSecInteractionNotAllowed: {
152             message = @"Interaction not allowed";
153             break;
154         }
155         case errSecDecode: {
156             message = @"Decoding failure";
157             break;
158         }
159         case errSecAuthFailed: {
160             message = @"Authentication failure";
161             break;
162         }
163         case -34018: {
164             message = @"iCloud Keychain failure";
165             break;
166         }
167         default: {
168             message = @"Unknown generic error";
169         }
170 #else
171         default:
172             message = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
173 #endif
174     }
176     return message;
180 static void SetAccessibilityForQuery(vlc_keystore *p_keystore,
181                                      NSMutableDictionary *query)
183     int accessibilityType = var_InheritInteger(p_keystore, "keychain-accessibility-type");
184     CFStringRef secattr;
185     switch (accessibilityType) {
186         case 1:
187             secattr = kSecAttrAccessibleAfterFirstUnlock;
188             break;
189         case 2:
190             secattr = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
191             break;
192         case 3:
193             secattr = kSecAttrAccessibleAlways;
194             break;
195         case 4:
196             secattr = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly;
197             break;
198         case 5:
199             secattr = kSecAttrAccessibleAlwaysThisDeviceOnly;
200             break;
201         case 6:
202             secattr = kSecAttrAccessibleWhenUnlocked;
203             break;
204         case 7:
205             secattr = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
206             break;
207         default:
208             secattr = nil;
209             break;
210     }
211     if (secattr != nil)
212         [query setObject:(__bridge id)secattr forKey:(__bridge id)kSecAttrAccessible];
215 struct vlc2secattr
217     const char *vlc;
218     const CFStringRef secattr;
221 static int vlc2secattr_cmp(const void *key_, const void *entry_)
223     const struct vlc2secattr *entry = entry_;
224     const char *key = key_;
225     return strcasecmp(key, entry->vlc);
228 static int SetAttributesForQuery(const char *const ppsz_values[KEY_MAX], NSMutableDictionary *query, const char *psz_label)
230     const char *psz_protocol = ppsz_values[KEY_PROTOCOL];
231     const char *psz_user = ppsz_values[KEY_USER];
232     const char *psz_server = ppsz_values[KEY_SERVER];
233     const char *psz_path = ppsz_values[KEY_PATH];
234     const char *psz_port = ppsz_values[KEY_PORT];
235     const char *psz_realm = ppsz_values[KEY_REALM];
236     const char *psz_authtype = ppsz_values[KEY_AUTHTYPE];
238     if (psz_label) {
239         [query setObject:[NSString stringWithUTF8String:psz_label] forKey:(__bridge id)kSecAttrLabel];
240     }
241     if (psz_protocol) {
242         const struct vlc2secattr tab[] =
243         { /* /!\ Alphabetical order /!\ */
244             { "ftp", kSecAttrProtocolFTP },
245             { "ftps", kSecAttrProtocolFTPS },
246             { "http", kSecAttrProtocolHTTP },
247             { "https", kSecAttrProtocolHTTPS },
248             { "rtsp", kSecAttrProtocolRTSP },
249             { "sftp", kSecAttrProtocolSSH },
250             { "smb", kSecAttrProtocolSMB },
251         };
253         const struct vlc2secattr *entry =
254             bsearch(psz_protocol, tab, ARRAY_SIZE(tab), sizeof(tab[0]), vlc2secattr_cmp);
255         if (!entry)
256             return VLC_EGENERIC;
258         [query setObject:(__bridge id)entry->secattr forKey:(__bridge id)kSecAttrProtocol];
259     }
260     if (psz_user) {
261         [query setObject:[NSString stringWithUTF8String:psz_user] forKey:(__bridge id)kSecAttrAccount];
262     }
263     if (psz_server) {
264         [query setObject:[NSString stringWithUTF8String:psz_server] forKey:(__bridge id)kSecAttrServer];
265     }
266     if (psz_path) {
267         [query setObject:[NSString stringWithUTF8String:psz_path] forKey:(__bridge id)kSecAttrPath];
268     }
269     if (psz_port) {
270         [query setObject:[NSNumber numberWithInt:atoi(psz_port)] forKey:(__bridge id)kSecAttrPort];
271     }
272     if (psz_realm) {
273         [query setObject:[NSString stringWithUTF8String:psz_realm] forKey:(__bridge id)kSecAttrSecurityDomain];
274     }
275     if (psz_authtype) {
276         if (strncasecmp(psz_protocol, "http", 4) == 0) {
277             const struct vlc2secattr tab[] =
278             { /* /!\ Alphabetical order /!\ */
279                 { "Basic", kSecAttrAuthenticationTypeHTTPBasic },
280                 { "Digest", kSecAttrAuthenticationTypeHTTPDigest },
281             };
282             const struct vlc2secattr *entry =
283                 bsearch(psz_authtype, tab, ARRAY_SIZE(tab), sizeof(tab[0]), vlc2secattr_cmp);
284             if (entry)
285                 [query setObject:(__bridge id)entry->secattr forKey:(__bridge id)kSecAttrAuthenticationType];
286         }
287         else if (strcasecmp(psz_protocol, "smb") == 0) {
288             if (strcmp(psz_authtype, "2") == 0)
289                 [query setObject:(__bridge id)kSecAttrAuthenticationTypeMSN forKey:(__bridge id)kSecAttrAuthenticationType];
290         }
291     }
293     return VLC_SUCCESS;
296 static int FillEntryValues(const NSDictionary *item, char *ppsz_values[KEY_MAX])
298     NSString *protocol = [item objectForKey:(__bridge id)kSecAttrProtocol];
299     if (protocol)
300     {
301         ppsz_values[KEY_PROTOCOL] = strdup([protocol UTF8String]);
302         if (!ppsz_values[KEY_PROTOCOL])
303             return VLC_ENOMEM;
304     }
306     NSString *user = [item objectForKey:(__bridge id)kSecAttrAccount];
307     if (user)
308     {
309         ppsz_values[KEY_USER] = strdup([user UTF8String]);
310         if (!ppsz_values[KEY_USER])
311             return VLC_ENOMEM;
312     }
314     NSString *server = [item objectForKey:(__bridge id)kSecAttrServer];
315     if (server)
316     {
317         ppsz_values[KEY_SERVER] = strdup([server UTF8String]);
318         if (!ppsz_values[KEY_SERVER])
319             return VLC_ENOMEM;
320     }
322     NSString *path = [item objectForKey:(__bridge id)kSecAttrPath];
323     if (path)
324     {
325         ppsz_values[KEY_PATH] = strdup([path UTF8String]);
326         if (!ppsz_values[KEY_PATH])
327             return VLC_ENOMEM;
328     }
330     NSNumber *port = [item objectForKey:(__bridge id)kSecAttrPort];
331     if (port)
332     {
333         ppsz_values[KEY_PORT] = strdup([[port stringValue] UTF8String]);
334         if (!ppsz_values[KEY_PORT])
335             return VLC_ENOMEM;
336     }
338     NSString *realm = [item objectForKey:(__bridge id)kSecAttrSecurityDomain];
339     if (realm)
340     {
341         ppsz_values[KEY_REALM] = strdup([realm UTF8String]);
342         if (!ppsz_values[KEY_REALM])
343             return VLC_ENOMEM;
344     }
346     const char *auth_val = NULL;
347     if ([protocol isEqualToString:(__bridge NSString*)kSecAttrProtocolHTTP]
348      || [protocol isEqualToString:(__bridge NSString*)kSecAttrProtocolHTTPS])
349     {
350         id authtype = [item objectForKey:(__bridge id)kSecAttrAuthenticationType];
351         if (authtype == (__bridge id)kSecAttrAuthenticationTypeHTTPBasic)
352             auth_val = "Basic";
353         else if (authtype == (__bridge id)kSecAttrAuthenticationTypeHTTPDigest)
354             auth_val = "Digest";
355     }
356     else if ([protocol isEqualToString:(__bridge NSString*)kSecAttrProtocolSMB])
357     {
358         id keytype = [item objectForKey:(__bridge id)kSecAttrAuthenticationType];
359         if (keytype == (__bridge id)kSecAttrAuthenticationTypeMSN)
360             auth_val = "2";
361     }
362     if (auth_val)
363     {
364         ppsz_values[KEY_AUTHTYPE] = strdup(auth_val);
365         if (!ppsz_values[KEY_AUTHTYPE])
366             return VLC_ENOMEM;
367     }
369     return VLC_SUCCESS;
372 static int Store(vlc_keystore *p_keystore,
373                  const char *const ppsz_values[KEY_MAX],
374                  const uint8_t *p_secret,
375                  size_t i_secret_len,
376                  const char *psz_label)
378     OSStatus status;
380     if (!ppsz_values[KEY_PROTOCOL] || !p_secret) {
381         return VLC_EGENERIC;
382     }
384     msg_Dbg(p_keystore, "Store keychain entry for server %s", ppsz_values[KEY_SERVER]);
386     NSMutableDictionary *query = nil;
387     NSMutableDictionary *searchQuery = CreateQuery(p_keystore);
389     /* set attributes */
390     if (SetAttributesForQuery(ppsz_values, searchQuery, psz_label))
391         return VLC_EGENERIC;
393     // One return type must be added for SecItemCopyMatching, even if not used.
394     // Older macOS versions (10.7) are very picky here...
395     [searchQuery setObject:@(YES) forKey:(__bridge id)kSecReturnRef];
396     CFTypeRef result = NULL;
398     /* search */
399     status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &result);
401     if (status == errSecSuccess) {
402         msg_Dbg(p_keystore, "the item was already known to keychain, so it will be updated");
403         /* item already existed in keychain, let's update */
404         query = [[NSMutableDictionary alloc] init];
406         /* just set the secret data */
407         NSData *secretData = [NSData dataWithBytes:p_secret length:i_secret_len];
408         [query setObject:secretData forKey:(__bridge id)kSecValueData];
409         [query setObject:@(kVlc4Creator) forKey:(__bridge id)kSecAttrCreator];
411         status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
412     } else if (status == errSecItemNotFound) {
413         msg_Dbg(p_keystore, "creating new item in keychain");
414         /* item not found, let's create! */
415         query = CreateQuery(p_keystore);
417         /* set attributes */
418         SetAttributesForQuery(ppsz_values, query, psz_label);
420         /* set accessibility */
421         SetAccessibilityForQuery(p_keystore, query);
423         /* set secret data */
424         NSData *secretData = [NSData dataWithBytes:p_secret length:i_secret_len];
425         [query setObject:secretData forKey:(__bridge id)kSecValueData];
426         [query setObject:@(kVlc4Creator) forKey:(__bridge id)kSecAttrCreator];
428         status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
429     }
430     if (status != errSecSuccess) {
431         msg_Err(p_keystore, "Storage failed (%i: '%s')", (int)status, [ErrorForStatus(status) UTF8String]);
432         return VLC_EGENERIC;
433     }
435     return VLC_SUCCESS;
438 static unsigned int Find(vlc_keystore *p_keystore,
439                          const char *const ppsz_values[KEY_MAX],
440                          vlc_keystore_entry **pp_entries)
442     CFTypeRef result = NULL;
443     NSMutableDictionary *baseLookupQuery = CreateQuery(p_keystore);
444     OSStatus status;
446     msg_Dbg(p_keystore, "Lookup keychain entry for server %s", ppsz_values[KEY_SERVER]);
448     /* set attributes */
449     if (SetAttributesForQuery(ppsz_values, baseLookupQuery, NULL))
450         return 0;
452     /* search */
453     NSMutableDictionary *searchQuery = [baseLookupQuery mutableCopy];
454     [searchQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
455     [searchQuery setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
457     status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &result);
458     if (status != errSecSuccess) {
459         msg_Warn(p_keystore, "lookup failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
460         return 0;
461     }
463     NSArray *listOfResults = (__bridge_transfer NSArray *)result;
464     NSUInteger count = listOfResults.count;
465     msg_Dbg(p_keystore, "found %lu result(s) for the provided attributes", (unsigned long)count);
467     vlc_keystore_entry *p_entries = calloc(count,
468                                            sizeof(vlc_keystore_entry));
469     if (!p_entries)
470         return 0;
472     for (NSUInteger i = 0; i < count; i++) {
473         vlc_keystore_entry *p_entry = &p_entries[i];
474         NSDictionary *keychainItem = [listOfResults objectAtIndex:i];
476         if (FillEntryValues(keychainItem, p_entry->ppsz_values))
477         {
478             vlc_keystore_release_entries(p_entries, 1);
479             return 0;
480         }
482         NSString *accountName = [keychainItem objectForKey:(__bridge id)kSecAttrAccount];
483         NSMutableDictionary *passwordFetchQuery = [baseLookupQuery mutableCopy];
484         [passwordFetchQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
485         [passwordFetchQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
486         if (accountName)
487             [passwordFetchQuery setObject:accountName forKey:(__bridge id)kSecAttrAccount];
489         CFTypeRef secretResult = NULL;
490         status = SecItemCopyMatching((__bridge CFDictionaryRef)passwordFetchQuery, &secretResult);
491         if (status != noErr) {
492             msg_Err(p_keystore, "Lookup error: %i (%s)", (int)status, [ErrorForStatus(status) UTF8String]);
493             vlc_keystore_release_entries(p_entries, (unsigned int)count);
494             return 0;
495         }
497         NSData *secretData = (__bridge_transfer NSData *)secretResult;
498         NSNumber *creator = [keychainItem objectForKey:(__bridge id)kSecAttrCreator];
499         if (creator && [creator isEqual:@(kVlc4Creator)]) {
500             msg_Dbg(p_keystore, "Found keychain entry in vlc4 format");
501             vlc_keystore_entry_set_secret(p_entry, secretData.bytes, secretData.length);
503         } else {
504             msg_Dbg(p_keystore, "Found keychain entry in vlc3 format");
506             /* we need to do some padding here, as string is expected to be 0 terminated */
507             NSUInteger secretDataLength = secretData.length;
508             uint8_t *paddedSecretData = calloc(1, secretDataLength + 1);
509             memcpy(paddedSecretData, secretData.bytes, secretDataLength);
510             vlc_keystore_entry_set_secret(p_entry, paddedSecretData, secretDataLength + 1);
511             free(paddedSecretData);
512         }
514         vlc_keystore_entry_set_secret(p_entry, secretData.bytes, secretData.length);
515     }
517     *pp_entries = p_entries;
519     return (unsigned int)count;
522 static unsigned int Remove(vlc_keystore *p_keystore,
523                            const char *const ppsz_values[KEY_MAX])
525     CFTypeRef result = NULL;
526     NSMutableDictionary *query = CreateQuery(p_keystore);
527     OSStatus status;
529     if (SetAttributesForQuery(ppsz_values, query, NULL))
530         return 0;
532     [query setObject:@(YES) forKey:(__bridge id)kSecReturnAttributes];
533     [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
535     /* do a copy matching to see how many items we are going to delete */
536     status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
537     if (status != errSecSuccess) {
538         msg_Warn(p_keystore, "lookup failed (%i: '%s')", (int)status, [ErrorForStatus(status) UTF8String]);
539         return 0;
540     }
542     NSArray *listOfResults = (__bridge_transfer NSArray *)result;
543     NSUInteger count = listOfResults.count;
544     msg_Dbg(p_keystore, "found %lu result(s) for the provided attributes",
545             (unsigned long)count);
547     /* delete everything!! */
548     status = SecItemDelete((__bridge CFDictionaryRef)query);
550     if (status != errSecSuccess) {
551         msg_Err(p_keystore, "deleting items matching the provided attributes failed");
552         return VLC_EGENERIC;
553     }
555     return (unsigned int)count;
558 static int Open(vlc_object_t *p_this)
560     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
562     p_keystore->p_sys = NULL;
563     p_keystore->pf_store = Store;
564     p_keystore->pf_find = Find;
565     p_keystore->pf_remove = Remove;
567     return VLC_SUCCESS;