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