Warning, /network/kdeconnect-ios/KDE Connect/KDE Connect/ObjC Backend/KeychainItemWrapper.m is written in an unsupported language. File is not indexed.

0001 /*
0002      File: KeychainItemWrapper.m 
0003  Abstract: 
0004  Objective-C wrapper for accessing a single keychain item.
0005   
0006   Version: 1.2 
0007   
0008  Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
0009  Inc. ("Apple") in consideration of your agreement to the following 
0010  terms, and your use, installation, modification or redistribution of 
0011  this Apple software constitutes acceptance of these terms.  If you do 
0012  not agree with these terms, please do not use, install, modify or 
0013  redistribute this Apple software. 
0014   
0015  In consideration of your agreement to abide by the following terms, and 
0016  subject to these terms, Apple grants you a personal, non-exclusive 
0017  license, under Apple's copyrights in this original Apple software (the 
0018  "Apple Software"), to use, reproduce, modify and redistribute the Apple 
0019  Software, with or without modifications, in source and/or binary forms; 
0020  provided that if you redistribute the Apple Software in its entirety and 
0021  without modifications, you must retain this notice and the following 
0022  text and disclaimers in all such redistributions of the Apple Software. 
0023  Neither the name, trademarks, service marks or logos of Apple Inc. may 
0024  be used to endorse or promote products derived from the Apple Software 
0025  without specific prior written permission from Apple.  Except as 
0026  expressly stated in this notice, no other rights or licenses, express or 
0027  implied, are granted by Apple herein, including but not limited to any 
0028  patent rights that may be infringed by your derivative works or by other 
0029  works in which the Apple Software may be incorporated. 
0030   
0031  The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
0032  MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
0033  THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
0034  FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
0035  OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
0036   
0037  IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
0038  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
0039  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
0040  INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
0041  MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
0042  AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
0043  STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
0044  POSSIBILITY OF SUCH DAMAGE. 
0045   
0046  Copyright (C) 2010 Apple Inc. All Rights Reserved. 
0047   
0048 */ 
0049 
0050 #import "KeychainItemWrapper.h"
0051 #import <Security/Security.h>
0052 #import "KDE_Connect-Swift.h"
0053 @import os.log;
0054 
0055 /*
0056 
0057 These are the default constants and their respective types,
0058 available for the kSecClassGenericPassword Keychain Item class:
0059 
0060 kSecAttrAccessGroup                     -               CFStringRef
0061 kSecAttrCreationDate            -               CFDateRef
0062 kSecAttrModificationDate    -           CFDateRef
0063 kSecAttrDescription                     -               CFStringRef
0064 kSecAttrComment                         -               CFStringRef
0065 kSecAttrCreator                         -               CFNumberRef
0066 kSecAttrType                -           CFNumberRef
0067 kSecAttrLabel                           -               CFStringRef
0068 kSecAttrIsInvisible                     -               CFBooleanRef
0069 kSecAttrIsNegative                      -               CFBooleanRef
0070 kSecAttrAccount                         -               CFStringRef
0071 kSecAttrService                         -               CFStringRef
0072 kSecAttrGeneric                         -               CFDataRef
0073  
0074 See the header file Security/SecItem.h for more details.
0075 
0076 */
0077 
0078 @interface KeychainItemWrapper (PrivateMethods)
0079 /*
0080 The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
0081 to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
0082 Keychain API expects as a validly constructed container class.
0083 */
0084 - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
0085 - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
0086 
0087 // Updates the item in the keychain, or adds it if it doesn't exist.
0088 - (void)writeToKeychain;
0089 
0090 @end
0091 
0092 @implementation KeychainItemWrapper
0093 
0094 @synthesize keychainItemData, genericPasswordQuery;
0095 
0096 - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
0097 {
0098     if (self = [super init])
0099     {
0100         // Begin Keychain search setup. The genericPasswordQuery leverages the special user
0101         // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
0102         // items which may be included by the same application.
0103         genericPasswordQuery = [[NSMutableDictionary alloc] init];
0104         
0105                 [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
0106         [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
0107                 
0108                 // The keychain access group attribute determines if this item can be shared
0109                 // amongst multiple apps whose code signing entitlements contain the same keychain access group.
0110                 if (accessGroup != nil)
0111                 {
0112 #if TARGET_IPHONE_SIMULATOR
0113                         // Ignore the access group if running on the iPhone simulator.
0114                         // 
0115                         // Apps that are built for the simulator aren't signed, so there's no keychain access group
0116                         // for the simulator to check. This means that all apps can see all keychain items when run
0117                         // on the simulator.
0118                         //
0119                         // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
0120                         // simulator will return -25243 (errSecNoAccessForItem).
0121 #else                   
0122                         [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
0123 #endif
0124                 }
0125                 
0126                 // Use the proper search constants, return only the attributes of the first match.
0127         [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
0128         [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
0129         
0130         NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
0131         
0132         NSMutableDictionary *outDictionary = nil;
0133         
0134         if (SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef)&outDictionary) != noErr)
0135         {
0136             // Stick these default values into keychain item if nothing found.
0137             [self resetKeychainItem];
0138                         
0139                         // Add the generic attribute and the keychain access group.
0140                         [keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
0141                         if (accessGroup != nil)
0142                         {
0143 #if TARGET_IPHONE_SIMULATOR
0144                                 // Ignore the access group if running on the iPhone simulator.
0145                                 // 
0146                                 // Apps that are built for the simulator aren't signed, so there's no keychain access group
0147                                 // for the simulator to check. This means that all apps can see all keychain items when run
0148                                 // on the simulator.
0149                                 //
0150                                 // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
0151                                 // simulator will return -25243 (errSecNoAccessForItem).
0152 #else                   
0153                                 [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
0154 #endif
0155                         }
0156                 }
0157         else
0158         {
0159             // load the saved data from Keychain.
0160             self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
0161         }
0162        
0163                 // [outDictionary release];
0164     }
0165     
0166         return self;
0167 }
0168 
0169 //- (void)dealloc
0170 //{
0171 //    [keychainItemData release];
0172 //    [genericPasswordQuery release];
0173 //
0174 //      [super dealloc];
0175 //}
0176 
0177 - (void)setObject:(id)inObject forKey:(id)key 
0178 {
0179     if (inObject == nil) return;
0180     id currentObject = [keychainItemData objectForKey:key];
0181     if (![currentObject isEqual:inObject])
0182     {
0183         [keychainItemData setObject:inObject forKey:key];
0184         [self writeToKeychain];
0185     }
0186 }
0187 
0188 - (id)objectForKey:(id)key
0189 {
0190     return [keychainItemData objectForKey:key];
0191 }
0192 
0193 - (void)resetKeychainItem
0194 {
0195         OSStatus junk = noErr;
0196     if (!keychainItemData) 
0197     {
0198         self.keychainItemData = [[NSMutableDictionary alloc] init];
0199     }
0200     else if (keychainItemData)
0201     {
0202         NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
0203                 junk = SecItemDelete((CFDictionaryRef)tempDictionary);
0204         NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
0205     }
0206     
0207     // Default attributes for keychain item.
0208     [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];
0209     [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];
0210     [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];
0211     
0212         // Default data for keychain item.
0213     [keychainItemData setObject:@"" forKey:(id)kSecValueData];
0214 }
0215 
0216 - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
0217 {
0218     // The assumption is that this method will be called with a properly populated dictionary
0219     // containing all the right key/value pairs for a SecItem.
0220     
0221     // Create a dictionary to return populated with the attributes and data.
0222     NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
0223     
0224     // Add the Generic Password keychain item class attribute.
0225     [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
0226     
0227     // Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
0228         // This is where to store sensitive data that should be encrypted.
0229     NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];
0230     [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
0231     
0232     return returnDictionary;
0233 }
0234 
0235 - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
0236 {
0237     // The assumption is that this method will be called with a properly populated dictionary
0238     // containing all the right key/value pairs for the UI element.
0239     
0240     // Create a dictionary to return populated with the attributes and data.
0241     NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
0242     
0243     // Add the proper search key and class attribute.
0244     [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
0245     [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
0246     
0247     // Acquire the password data from the attributes.
0248     NSData *passwordData = NULL;
0249     if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef)&passwordData) == noErr)
0250     {
0251         // Remove the search, class, and identifier key/value, we don't need them anymore.
0252         [returnDictionary removeObjectForKey:(id)kSecReturnData];
0253         
0254         // Add the password to the dictionary, converting from NSData to NSString.
0255         NSString *password = [[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]
0256                                                      encoding:NSUTF8StringEncoding];
0257         [returnDictionary setObject:password forKey:(id)kSecValueData];
0258     }
0259     else
0260     {
0261         // Don't do anything if nothing is found.
0262         NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
0263     }
0264     
0265     // [passwordData release];
0266    
0267         return returnDictionary;
0268 }
0269 
0270 - (void)writeToKeychain
0271 {
0272     NSDictionary *attributes = NULL;
0273     NSMutableDictionary *updateItem = NULL;
0274         OSStatus result;
0275     
0276     if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef)&attributes) == noErr)
0277     {
0278         // First we need the attributes from the Keychain.
0279         updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
0280         // Second we need to add the appropriate search key/values.
0281         [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
0282         
0283         // Lastly, we need to set up the updated attribute list being careful to remove the class.
0284         NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
0285         [tempCheck removeObjectForKey:(id)kSecClass];
0286                 
0287 #if TARGET_IPHONE_SIMULATOR
0288                 // Remove the access group if running on the iPhone simulator.
0289                 // 
0290                 // Apps that are built for the simulator aren't signed, so there's no keychain access group
0291                 // for the simulator to check. This means that all apps can see all keychain items when run
0292                 // on the simulator.
0293                 //
0294                 // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
0295                 // simulator will return -25243 (errSecNoAccessForItem).
0296                 //
0297                 // The access group attribute will be included in items returned by SecItemCopyMatching,
0298                 // which is why we need to remove it before updating the item.
0299                 [tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
0300 #endif
0301         
0302         // An implicit assumption is that you can only update a single item at a time.
0303                 
0304         result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
0305                 NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
0306     }
0307     else
0308     {
0309         // No previous item found; add the new one.
0310         result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
0311         
0312         os_log_t logger = os_log_create([NSString kdeConnectOSLogSubsystem].UTF8String,
0313                                         NSStringFromClass([self class]).UTF8String);
0314         os_log_with_type(logger, OS_LOG_TYPE_INFO, "Result %d", result);
0315         // NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
0316     }
0317 }
0318 
0319 @end