Browse Source

Add KeychainItemWrapper under standard Apple sample code boilerplate license

Felix Paul Kühne 10 years ago
parent
commit
3d8775c886
3 changed files with 403 additions and 2 deletions
  1. 74 0
      Sources/KeychainItemWrapper.h
  2. 313 0
      Sources/KeychainItemWrapper.m
  3. 16 2
      VLC for iOS.xcodeproj/project.pbxproj

+ 74 - 0
Sources/KeychainItemWrapper.h

@@ -0,0 +1,74 @@
+/*
+     File: KeychainItemWrapper.h
+ Abstract: 
+ Objective-C wrapper for accessing a single keychain item.
+ 
+  Version: 1.2
+ 
+ Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
+ Inc. ("Apple") in consideration of your agreement to the following
+ terms, and your use, installation, modification or redistribution of
+ this Apple software constitutes acceptance of these terms.  If you do
+ not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+ 
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, Apple grants you a personal, non-exclusive
+ license, under Apple's copyrights in this original Apple software (the
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple
+ Software, with or without modifications, in source and/or binary forms;
+ provided that if you redistribute the Apple Software in its entirety and
+ without modifications, you must retain this notice and the following
+ text and disclaimers in all such redistributions of the Apple Software.
+ Neither the name, trademarks, service marks or logos of Apple Inc. may
+ be used to endorse or promote products derived from the Apple Software
+ without specific prior written permission from Apple.  Except as
+ expressly stated in this notice, no other rights or licenses, express or
+ implied, are granted by Apple herein, including but not limited to any
+ patent rights that may be infringed by your derivative works or by other
+ works in which the Apple Software may be incorporated.
+ 
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ 
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ 
+ Copyright (C) 2010 Apple Inc. All Rights Reserved.
+ 
+*/
+
+#import <UIKit/UIKit.h>
+
+/*
+    The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a 
+    simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain
+    CF/NS container objects.
+*/
+@interface KeychainItemWrapper : NSObject
+{
+    NSMutableDictionary *keychainItemData;		// The actual keychain item data backing store.
+    NSMutableDictionary *genericPasswordQuery;	// A placeholder for the generic keychain item query used to locate the item.
+}
+
+@property (nonatomic, retain) NSMutableDictionary *keychainItemData;
+@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;
+
+// Designated initializer.
+- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
+- (void)setObject:(id)inObject forKey:(id)key;
+- (id)objectForKey:(id)key;
+
+// Initializes and resets the default generic keychain item data.
+- (void)resetKeychainItem;
+
+@end

+ 313 - 0
Sources/KeychainItemWrapper.m

@@ -0,0 +1,313 @@
+/*
+     File: KeychainItemWrapper.m 
+ Abstract: 
+ Objective-C wrapper for accessing a single keychain item.
+  
+  Version: 1.2 
+  
+ Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
+ Inc. ("Apple") in consideration of your agreement to the following 
+ terms, and your use, installation, modification or redistribution of 
+ this Apple software constitutes acceptance of these terms.  If you do 
+ not agree with these terms, please do not use, install, modify or 
+ redistribute this Apple software. 
+  
+ In consideration of your agreement to abide by the following terms, and 
+ subject to these terms, Apple grants you a personal, non-exclusive 
+ license, under Apple's copyrights in this original Apple software (the 
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple 
+ Software, with or without modifications, in source and/or binary forms; 
+ provided that if you redistribute the Apple Software in its entirety and 
+ without modifications, you must retain this notice and the following 
+ text and disclaimers in all such redistributions of the Apple Software. 
+ Neither the name, trademarks, service marks or logos of Apple Inc. may 
+ be used to endorse or promote products derived from the Apple Software 
+ without specific prior written permission from Apple.  Except as 
+ expressly stated in this notice, no other rights or licenses, express or 
+ implied, are granted by Apple herein, including but not limited to any 
+ patent rights that may be infringed by your derivative works or by other 
+ works in which the Apple Software may be incorporated. 
+  
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
+  
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
+ POSSIBILITY OF SUCH DAMAGE. 
+  
+ Copyright (C) 2010 Apple Inc. All Rights Reserved. 
+  
+*/ 
+
+#import "KeychainItemWrapper.h"
+#import <Security/Security.h>
+
+/*
+
+These are the default constants and their respective types,
+available for the kSecClassGenericPassword Keychain Item class:
+
+kSecAttrAccessGroup			-		CFStringRef
+kSecAttrCreationDate		-		CFDateRef
+kSecAttrModificationDate    -		CFDateRef
+kSecAttrDescription			-		CFStringRef
+kSecAttrComment				-		CFStringRef
+kSecAttrCreator				-		CFNumberRef
+kSecAttrType                -		CFNumberRef
+kSecAttrLabel				-		CFStringRef
+kSecAttrIsInvisible			-		CFBooleanRef
+kSecAttrIsNegative			-		CFBooleanRef
+kSecAttrAccount				-		CFStringRef
+kSecAttrService				-		CFStringRef
+kSecAttrGeneric				-		CFDataRef
+ 
+See the header file Security/SecItem.h for more details.
+
+*/
+
+@interface KeychainItemWrapper (PrivateMethods)
+/*
+The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
+to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
+Keychain API expects as a validly constructed container class.
+*/
+- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
+- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
+
+// Updates the item in the keychain, or adds it if it doesn't exist.
+- (void)writeToKeychain;
+
+@end
+
+@implementation KeychainItemWrapper
+
+@synthesize keychainItemData, genericPasswordQuery;
+
+- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
+{
+    if (self = [super init])
+    {
+        // Begin Keychain search setup. The genericPasswordQuery leverages the special user
+        // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
+        // items which may be included by the same application.
+        genericPasswordQuery = [[NSMutableDictionary alloc] init];
+        
+		[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
+        [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
+		
+		// The keychain access group attribute determines if this item can be shared
+		// amongst multiple apps whose code signing entitlements contain the same keychain access group.
+		if (accessGroup != nil)
+		{
+#if TARGET_IPHONE_SIMULATOR
+			// Ignore the access group if running on the iPhone simulator.
+			// 
+			// Apps that are built for the simulator aren't signed, so there's no keychain access group
+			// for the simulator to check. This means that all apps can see all keychain items when run
+			// on the simulator.
+			//
+			// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
+			// simulator will return -25243 (errSecNoAccessForItem).
+#else			
+			[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
+#endif
+		}
+		
+		// Use the proper search constants, return only the attributes of the first match.
+        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
+        [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
+        
+        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
+        
+        NSMutableDictionary *outDictionary = nil;
+        
+        if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
+        {
+            // Stick these default values into keychain item if nothing found.
+            [self resetKeychainItem];
+			
+			// Add the generic attribute and the keychain access group.
+			[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
+			if (accessGroup != nil)
+			{
+#if TARGET_IPHONE_SIMULATOR
+				// Ignore the access group if running on the iPhone simulator.
+				// 
+				// Apps that are built for the simulator aren't signed, so there's no keychain access group
+				// for the simulator to check. This means that all apps can see all keychain items when run
+				// on the simulator.
+				//
+				// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
+				// simulator will return -25243 (errSecNoAccessForItem).
+#else			
+				[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
+#endif
+			}
+		}
+        else
+        {
+            // load the saved data from Keychain.
+            self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
+        }
+       
+		[outDictionary release];
+    }
+    
+	return self;
+}
+
+- (void)dealloc
+{
+    [keychainItemData release];
+    [genericPasswordQuery release];
+    
+	[super dealloc];
+}
+
+- (void)setObject:(id)inObject forKey:(id)key 
+{
+    if (inObject == nil) return;
+    id currentObject = [keychainItemData objectForKey:key];
+    if (![currentObject isEqual:inObject])
+    {
+        [keychainItemData setObject:inObject forKey:key];
+        [self writeToKeychain];
+    }
+}
+
+- (id)objectForKey:(id)key
+{
+    return [keychainItemData objectForKey:key];
+}
+
+- (void)resetKeychainItem
+{
+	OSStatus junk = noErr;
+    if (!keychainItemData) 
+    {
+        self.keychainItemData = [[NSMutableDictionary alloc] init];
+    }
+    else if (keychainItemData)
+    {
+        NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
+		junk = SecItemDelete((CFDictionaryRef)tempDictionary);
+        NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
+    }
+    
+    // Default attributes for keychain item.
+    [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];
+    [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];
+    [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];
+    
+	// Default data for keychain item.
+    [keychainItemData setObject:@"" forKey:(id)kSecValueData];
+}
+
+- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
+{
+    // The assumption is that this method will be called with a properly populated dictionary
+    // containing all the right key/value pairs for a SecItem.
+    
+    // Create a dictionary to return populated with the attributes and data.
+    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
+    
+    // Add the Generic Password keychain item class attribute.
+    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
+    
+    // Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
+	// This is where to store sensitive data that should be encrypted.
+    NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];
+    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
+    
+    return returnDictionary;
+}
+
+- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
+{
+    // The assumption is that this method will be called with a properly populated dictionary
+    // containing all the right key/value pairs for the UI element.
+    
+    // Create a dictionary to return populated with the attributes and data.
+    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
+    
+    // Add the proper search key and class attribute.
+    [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
+    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
+    
+    // Acquire the password data from the attributes.
+    NSData *passwordData = NULL;
+    if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
+    {
+        // Remove the search, class, and identifier key/value, we don't need them anymore.
+        [returnDictionary removeObjectForKey:(id)kSecReturnData];
+        
+        // Add the password to the dictionary, converting from NSData to NSString.
+        NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] 
+                                                     encoding:NSUTF8StringEncoding] autorelease];
+        [returnDictionary setObject:password forKey:(id)kSecValueData];
+    }
+    else
+    {
+        // Don't do anything if nothing is found.
+        NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
+    }
+    
+    [passwordData release];
+   
+	return returnDictionary;
+}
+
+- (void)writeToKeychain
+{
+    NSDictionary *attributes = NULL;
+    NSMutableDictionary *updateItem = NULL;
+	OSStatus result;
+    
+    if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
+    {
+        // First we need the attributes from the Keychain.
+        updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
+        // Second we need to add the appropriate search key/values.
+        [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
+        
+        // Lastly, we need to set up the updated attribute list being careful to remove the class.
+        NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
+        [tempCheck removeObjectForKey:(id)kSecClass];
+		
+#if TARGET_IPHONE_SIMULATOR
+		// Remove the access group if running on the iPhone simulator.
+		// 
+		// Apps that are built for the simulator aren't signed, so there's no keychain access group
+		// for the simulator to check. This means that all apps can see all keychain items when run
+		// on the simulator.
+		//
+		// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
+		// simulator will return -25243 (errSecNoAccessForItem).
+		//
+		// The access group attribute will be included in items returned by SecItemCopyMatching,
+		// which is why we need to remove it before updating the item.
+		[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
+#endif
+        
+        // An implicit assumption is that you can only update a single item at a time.
+		
+        result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
+		NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
+    }
+    else
+    {
+        // No previous item found; add the new one.
+        result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
+		NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
+    }
+}
+
+@end

+ 16 - 2
VLC for iOS.xcodeproj/project.pbxproj

@@ -167,6 +167,7 @@
 		7DB638AB185BC0890003887C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7DB638AA185BC0890003887C /* Images.xcassets */; };
 		7DB847D71A5871570002DC30 /* VLCOneDriveObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB847D61A5871570002DC30 /* VLCOneDriveObject.m */; };
 		7DBB788A1B30423B00894467 /* VLCKeychainCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBB78891B30423B00894467 /* VLCKeychainCoordinator.m */; };
+		7DBB788D1B305D7E00894467 /* KeychainItemWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBB788C1B305D7E00894467 /* KeychainItemWrapper.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
 		7DBBF182183AB3B80009A339 /* VLCAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBBF181183AB3B80009A339 /* VLCAppDelegate.m */; };
 		7DBBF19A183AB4300009A339 /* VLCCloudStorageTableViewCell~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7DBBF185183AB4300009A339 /* VLCCloudStorageTableViewCell~ipad.xib */; };
 		7DBBF19B183AB4300009A339 /* VLCCloudStorageTableViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7DBBF186183AB4300009A339 /* VLCCloudStorageTableViewCell~iphone.xib */; };
@@ -611,6 +612,8 @@
 		7DB847D61A5871570002DC30 /* VLCOneDriveObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCOneDriveObject.m; path = Sources/VLCOneDriveObject.m; sourceTree = SOURCE_ROOT; };
 		7DBB78881B30423B00894467 /* VLCKeychainCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCKeychainCoordinator.h; path = Sources/VLCKeychainCoordinator.h; sourceTree = SOURCE_ROOT; };
 		7DBB78891B30423B00894467 /* VLCKeychainCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCKeychainCoordinator.m; path = Sources/VLCKeychainCoordinator.m; sourceTree = SOURCE_ROOT; };
+		7DBB788B1B305D7E00894467 /* KeychainItemWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeychainItemWrapper.h; path = Sources/KeychainItemWrapper.h; sourceTree = SOURCE_ROOT; };
+		7DBB788C1B305D7E00894467 /* KeychainItemWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KeychainItemWrapper.m; path = Sources/KeychainItemWrapper.m; sourceTree = SOURCE_ROOT; };
 		7DBBF180183AB3B80009A339 /* VLCAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCAppDelegate.h; path = Sources/VLCAppDelegate.h; sourceTree = SOURCE_ROOT; };
 		7DBBF181183AB3B80009A339 /* VLCAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCAppDelegate.m; path = Sources/VLCAppDelegate.m; sourceTree = SOURCE_ROOT; };
 		7DBBF185183AB4300009A339 /* VLCCloudStorageTableViewCell~ipad.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = "VLCCloudStorageTableViewCell~ipad.xib"; path = "Resources/VLCCloudStorageTableViewCell~ipad.xib"; sourceTree = SOURCE_ROOT; };
@@ -1298,8 +1301,7 @@
 				7D6B08BB174A72A900A05173 /* VLCConstants.h */,
 				7DBBF180183AB3B80009A339 /* VLCAppDelegate.h */,
 				7DBBF181183AB3B80009A339 /* VLCAppDelegate.m */,
-				7DBB78881B30423B00894467 /* VLCKeychainCoordinator.h */,
-				7DBB78891B30423B00894467 /* VLCKeychainCoordinator.m */,
+				7DBB788E1B305D8300894467 /* Keychain */,
 				A7D03A4817A4249F0022C16F /* MediaDiscovering */,
 				7D2339AB176DE70E008D223C /* Menu */,
 				7D5F7ABA175265CB006CCCFA /* HTTP Connectivity */,
@@ -1386,6 +1388,17 @@
 			name = "Server browsing";
 			sourceTree = "<group>";
 		};
+		7DBB788E1B305D8300894467 /* Keychain */ = {
+			isa = PBXGroup;
+			children = (
+				7DBB78881B30423B00894467 /* VLCKeychainCoordinator.h */,
+				7DBB78891B30423B00894467 /* VLCKeychainCoordinator.m */,
+				7DBB788B1B305D7E00894467 /* KeychainItemWrapper.h */,
+				7DBB788C1B305D7E00894467 /* KeychainItemWrapper.m */,
+			);
+			name = Keychain;
+			sourceTree = "<group>";
+		};
 		7DC19AEB1868C91400810BF7 /* First Steps */ = {
 			isa = PBXGroup;
 			children = (
@@ -1965,6 +1978,7 @@
 				7D50903218F41C7900180139 /* VLCAlertView.m in Sources */,
 				7DBBF182183AB3B80009A339 /* VLCAppDelegate.m in Sources */,
 				7D3784C0183A9938009EE944 /* VLCLinearProgressIndicator.m in Sources */,
+				7DBB788D1B305D7E00894467 /* KeychainItemWrapper.m in Sources */,
 				DD3EA6311AF50CFE007FF096 /* VLCWatchMessage.m in Sources */,
 				41CD695D1A29D72600E60BCE /* VLCBoxTableViewController.m in Sources */,
 				7D3784C1183A9938009EE944 /* VLCMenuButton.m in Sources */,