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