UPnP: split sat>ip parsing function
[vlc.git] / modules / keystore / keychain.m
blob8eec53ba09c11978ce2f77f79b915af4dd3c4441
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 #if TARGET_OS_IPHONE
36   #import <Foundation/Foundation.h>
37   #define OSX_MAVERICKS 1
38 #else
39   #import <Cocoa/Cocoa.h>
40   #define OSX_MAVERICKS (NSAppKitVersionNumber >= 1265)
41 #endif
43 #import <Security/Security.h>
45 static int Open(vlc_object_t *);
47 static const int sync_list[] =
48 { 0, 1, 2 };
49 static const char *const sync_list_text[] = {
50     N_("Yes"), N_("No"), N_("Any")
53 static const int accessibility_list[] =
54 { 0, 1, 2, 3, 4, 5, 6, 7 };
55 static const char *const accessibility_list_text[] = {
56     N_("System default"),
57     N_("After first unlock"),
58     N_("After first unlock, on this device only"),
59     N_("Always"),
60     N_("When passcode set, on this device only"),
61     N_("Always, on this device only"),
62     N_("When unlocked"),
63     N_("When unlocked, on this device only")
66 #define SYNC_ITEMS_TEXT N_("Synchronize stored items")
67 #define SYNC_ITEMS_LONGTEXT N_("Synchronizes stored items via iCloud Keychain if enabled in the user domain.")
69 #define ACCESSIBILITY_TYPE_TEXT N_("Accessibility type for all future passwords saved to the Keychain")
71 #define ACCESS_GROUP_TEXT N_("Keychain access group")
72 #define ACCESS_GROUP_LONGTEXT N_("Keychain access group as defined by the app entitlements.")
74 /* VLC can be compiled against older SDKs (like before OS X 10.10)
75  * but newer features should still be available.
76  * Hence, re-define things as needed */
77 #ifndef kSecAttrSynchronizable
78 #define kSecAttrSynchronizable CFSTR("sync")
79 #endif
81 #ifndef kSecAttrSynchronizableAny
82 #define kSecAttrSynchronizableAny CFSTR("syna")
83 #endif
85 #ifndef kSecAttrAccessGroup
86 #define kSecAttrAccessGroup CFSTR("agrp")
87 #endif
89 #ifndef kSecAttrAccessibleAfterFirstUnlock
90 #define kSecAttrAccessibleAfterFirstUnlock CFSTR("ck")
91 #endif
93 #ifndef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
94 #define kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly CFSTR("cku")
95 #endif
97 #ifndef kSecAttrAccessibleAlways
98 #define kSecAttrAccessibleAlways CFSTR("dk")
99 #endif
101 #ifndef kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
102 #define kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly CFSTR("akpu")
103 #endif
105 #ifndef kSecAttrAccessibleAlwaysThisDeviceOnly
106 #define kSecAttrAccessibleAlwaysThisDeviceOnly CFSTR("dku")
107 #endif
109 #ifndef kSecAttrAccessibleWhenUnlocked
110 #define kSecAttrAccessibleWhenUnlocked CFSTR("ak")
111 #endif
113 #ifndef kSecAttrAccessibleWhenUnlockedThisDeviceOnly
114 #define kSecAttrAccessibleWhenUnlockedThisDeviceOnly CFSTR("aku")
115 #endif
117 vlc_module_begin()
118     set_shortname(N_("Keychain keystore"))
119     set_description(N_("Keystore for iOS, macOS and tvOS"))
120     set_category(CAT_ADVANCED)
121     set_subcategory(SUBCAT_ADVANCED_MISC)
122     add_integer("keychain-synchronize", 1, SYNC_ITEMS_TEXT, SYNC_ITEMS_LONGTEXT, true)
123     change_integer_list(sync_list, sync_list_text)
124     add_integer("keychain-accessibility-type", 0, ACCESSIBILITY_TYPE_TEXT, ACCESSIBILITY_TYPE_TEXT, true)
125     change_integer_list(accessibility_list, accessibility_list_text)
126     add_string("keychain-access-group", NULL, ACCESS_GROUP_TEXT, ACCESS_GROUP_LONGTEXT, true)
127     set_capability("keystore", 100)
128     set_callbacks(Open, NULL)
129 vlc_module_end ()
131 static NSMutableDictionary * CreateQuery(vlc_keystore *p_keystore)
133     NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
134     [dictionary setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
136     [dictionary setObject:@"VLC-Password-Service" forKey:(__bridge id)kSecAttrService];
138     const char * psz_access_group = var_InheritString(p_keystore, "keychain-access-group");
139     if (psz_access_group) {
140         [dictionary setObject:[NSString stringWithUTF8String:psz_access_group] forKey:(__bridge id)kSecAttrAccessGroup];
141     }
143     id syncValue;
144     int syncMode = var_InheritInteger(p_keystore, "keychain-synchronize");
146     if (syncMode == 2) {
147         syncValue = (__bridge id)kSecAttrSynchronizableAny;
148     } else if (syncMode == 0) {
149         syncValue = @(YES);
150     } else {
151         syncValue = @(NO);
152     }
154     [dictionary setObject:syncValue forKey:(__bridge id)(kSecAttrSynchronizable)];
156     return dictionary;
159 static NSString * ErrorForStatus(OSStatus status)
161     NSString *message = nil;
163     switch (status) {
164 #if TARGET_OS_IPHONE
165         case errSecUnimplemented: {
166             message = @"Query unimplemented";
167             break;
168         }
169         case errSecParam: {
170             message = @"Faulty parameter";
171             break;
172         }
173         case errSecAllocate: {
174             message = @"Allocation failure";
175             break;
176         }
177         case errSecNotAvailable: {
178             message = @"Query not available";
179             break;
180         }
181         case errSecDuplicateItem: {
182             message = @"Duplicated item";
183             break;
184         }
185         case errSecItemNotFound: {
186             message = @"Item not found";
187             break;
188         }
189         case errSecInteractionNotAllowed: {
190             message = @"Interaction not allowed";
191             break;
192         }
193         case errSecDecode: {
194             message = @"Decoding failure";
195             break;
196         }
197         case errSecAuthFailed: {
198             message = @"Authentication failure";
199             break;
200         }
201         case -34018: {
202             message = @"iCloud Keychain failure";
203             break;
204         }
205         default: {
206             message = @"Unknown generic error";
207         }
208 #else
209         default:
210             message = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
211 #endif
212     }
214     return message;
217 extern const CFStringRef kSecAttrAccessible;
219 #pragma clang diagnostic push
220 #pragma clang diagnostic ignored "-Wpartial-availability"
221 static void SetAccessibilityForQuery(vlc_keystore *p_keystore,
222                                      NSMutableDictionary *query)
224     if (!OSX_MAVERICKS)
225         return;
227     int accessibilityType = var_InheritInteger(p_keystore, "keychain-accessibility-type");
228     switch (accessibilityType) {
229         case 1:
230             [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible];
231             break;
232         case 2:
233             [query setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
234             break;
235         case 3:
236             [query setObject:(__bridge id)kSecAttrAccessibleAlways forKey:(__bridge id)kSecAttrAccessible];
237             break;
238         case 4:
239             [query setObject:(__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
240             break;
241         case 5:
242             [query setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
243             break;
244         case 6:
245             [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
246             break;
247         case 7:
248             [query setObject:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
249             break;
250         default:
251             break;
252     }
254 #pragma clang diagnostic pop
256 static void SetAttributesForQuery(const char *const ppsz_values[KEY_MAX], NSMutableDictionary *query, const char *psz_label)
258     const char *psz_protocol = ppsz_values[KEY_PROTOCOL];
259     const char *psz_user = ppsz_values[KEY_USER];
260     const char *psz_server = ppsz_values[KEY_SERVER];
261     const char *psz_path = ppsz_values[KEY_PATH];
262     const char *psz_port = ppsz_values[KEY_PORT];
264     if (psz_label) {
265         [query setObject:[NSString stringWithUTF8String:psz_label] forKey:(__bridge id)kSecAttrLabel];
266     }
267     if (psz_protocol) {
268         [query setObject:[NSString stringWithUTF8String:psz_protocol] forKey:(__bridge id)kSecAttrProtocol];
269     }
270     if (psz_user) {
271         [query setObject:[NSString stringWithUTF8String:psz_user] forKey:(__bridge id)kSecAttrAccount];
272     }
273     if (psz_server) {
274         [query setObject:[NSString stringWithUTF8String:psz_server] forKey:(__bridge id)kSecAttrServer];
275     }
276     if (psz_path) {
277         [query setObject:[NSString stringWithUTF8String:psz_path] forKey:(__bridge id)kSecAttrPath];
278     }
279     if (psz_port) {
280         [query setObject:[NSNumber numberWithInt:atoi(psz_port)] forKey:(__bridge id)kSecAttrPort];
281     }
284 static int Store(vlc_keystore *p_keystore,
285                  const char *const ppsz_values[KEY_MAX],
286                  const uint8_t *p_secret,
287                  size_t i_secret_len,
288                  const char *psz_label)
290     OSStatus status;
292     if (!ppsz_values[KEY_PROTOCOL] || !p_secret) {
293         return VLC_EGENERIC;
294     }
296     NSMutableDictionary *query = nil;
297     NSMutableDictionary *searchQuery = CreateQuery(p_keystore);
299     /* set attributes */
300     SetAttributesForQuery(ppsz_values, searchQuery, psz_label);
302     // One return type must be added for SecItemCopyMatching, even if not used.
303     // Older macOS versions (10.7) are very picky here...
304     [searchQuery setObject:@(YES) forKey:(__bridge id)kSecReturnRef];
305     CFTypeRef result = NULL;
307     /* search */
308     status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &result);
309     /* create storage unit */
310     NSData *secretData = [[NSString stringWithFormat:@"%s", p_secret] dataUsingEncoding:NSUTF8StringEncoding];
312     if (status == errSecSuccess) {
313         msg_Dbg(p_keystore, "the item was already known to keychain, so it will be updated");
314         /* item already existed in keychain, let's update */
315         query = [[NSMutableDictionary alloc] init];
317         /* just set the secret data */
318         [query setObject:secretData forKey:(__bridge id)kSecValueData];
320         status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
321     } else if (status == errSecItemNotFound) {
322         msg_Dbg(p_keystore, "creating new item in keychain");
323         /* item not found, let's create! */
324         query = CreateQuery(p_keystore);
326         /* set attributes */
327         SetAttributesForQuery(ppsz_values, query, psz_label);
329         /* set accessibility */
330         SetAccessibilityForQuery(p_keystore, query);
332         /* set secret data */
333         [query setObject:secretData forKey:(__bridge id)kSecValueData];
335         status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
336     }
337     if (status != errSecSuccess) {
338         msg_Err(p_keystore, "Storage failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
339         return VLC_EGENERIC;
340     }
342     return VLC_SUCCESS;
345 static unsigned int Find(vlc_keystore *p_keystore,
346                          const char *const ppsz_values[KEY_MAX],
347                          vlc_keystore_entry **pp_entries)
349     CFTypeRef result = NULL;
350     NSMutableDictionary *baseLookupQuery = CreateQuery(p_keystore);
351     OSStatus status;
353     /* set attributes */
354     SetAttributesForQuery(ppsz_values, baseLookupQuery, NULL);
356     /* search */
357     NSMutableDictionary *searchQuery = [baseLookupQuery mutableCopy];
358     [searchQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
359     [searchQuery setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
361     status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &result);
362     if (status != errSecSuccess) {
363         msg_Warn(p_keystore, "lookup failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
364         return 0;
365     }
367     NSArray *listOfResults = (__bridge_transfer NSArray *)result;
368     NSUInteger count = listOfResults.count;
369     msg_Dbg(p_keystore, "found %lu result(s) for the provided attributes", count);
371     vlc_keystore_entry *p_entries = calloc(count,
372                                            sizeof(vlc_keystore_entry));
373     if (!p_entries)
374         return 0;
376     for (NSUInteger i = 0; i < count; i++) {
377         vlc_keystore_entry *p_entry = &p_entries[i];
378         if (ks_values_copy((const char **)p_entry->ppsz_values, ppsz_values) != VLC_SUCCESS) {
379             vlc_keystore_release_entries(p_entries, 1);
380             return 0;
381         }
383         NSDictionary *keychainItem = [listOfResults objectAtIndex:i];
384         NSString *accountName = [keychainItem objectForKey:(__bridge id)kSecAttrAccount];
385         NSMutableDictionary *passwordFetchQuery = [baseLookupQuery mutableCopy];
386         [passwordFetchQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
387         [passwordFetchQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
388         [passwordFetchQuery setObject:accountName forKey:(__bridge id)kSecAttrAccount];
390         CFTypeRef secretResult = NULL;
391         status = SecItemCopyMatching((__bridge CFDictionaryRef)passwordFetchQuery, &secretResult);
392         if (status != noErr) {
393             msg_Err(p_keystore, "Lookup error: %i (%s)", status, [ErrorForStatus(status) UTF8String]);
394             vlc_keystore_release_entries(p_entries, (unsigned int)count);
395             return 0;
396         }
398         if (!p_entry->ppsz_values[KEY_USER]) {
399             msg_Dbg(p_keystore, "using account name from the keychain for login");
400             p_entry->ppsz_values[KEY_USER] = strdup([accountName UTF8String]);
401         }
403         NSData *secretData = (__bridge_transfer NSData *)secretResult;
404         NSUInteger secretDataLength = secretData.length;
406         /* we need to do some padding here, as string is expected to be 0 terminated */
407         uint8_t *paddedSecretData = calloc(1, secretDataLength + 1);
408         memcpy(paddedSecretData, secretData.bytes, secretDataLength);
409         vlc_keystore_entry_set_secret(p_entry, paddedSecretData, secretDataLength + 1);
410         free(paddedSecretData);
411     }
413     *pp_entries = p_entries;
415     return (unsigned int)count;
418 static unsigned int Remove(vlc_keystore *p_keystore,
419                            const char *const ppsz_values[KEY_MAX])
421     CFTypeRef result = NULL;
422     NSMutableDictionary *query = CreateQuery(p_keystore);
423     OSStatus status;
425     SetAttributesForQuery(ppsz_values, query, NULL);
427     [query setObject:@(YES) forKey:(__bridge id)kSecReturnAttributes];
428     [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
430     /* do a copy matching to see how many items we are going to delete */
431     status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
432     if (status != errSecSuccess) {
433         msg_Warn(p_keystore, "lookup failed (%i: '%s')", status, [ErrorForStatus(status) UTF8String]);
434         return 0;
435     }
437     NSArray *listOfResults = (__bridge_transfer NSArray *)result;
438     NSUInteger count = listOfResults.count;
439     msg_Dbg(p_keystore, "found %lu result(s) for the provided attributes", count);
441     /* delete everything!! */
442     status = SecItemDelete((__bridge CFDictionaryRef)query);
444     if (status != errSecSuccess) {
445         msg_Err(p_keystore, "deleting items matching the provided attributes failed");
446         return VLC_EGENERIC;
447     }
449     return (unsigned int)count;
452 static int Open(vlc_object_t *p_this)
454     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
456     p_keystore->p_sys = NULL;
457     p_keystore->pf_store = Store;
458     p_keystore->pf_find = Find;
459     p_keystore->pf_remove = Remove;
461     return VLC_SUCCESS;