Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
501
third_party/FreeRDP/client/iOS/Misc/SFHFKeychainUtils.m
vendored
Normal file
501
third_party/FreeRDP/client/iOS/Misc/SFHFKeychainUtils.m
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
//
|
||||
// SFHFKeychainUtils.m
|
||||
//
|
||||
// Created by Buzz Andersen on 10/20/08.
|
||||
// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
|
||||
// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
#import "SFHFKeychainUtils.h"
|
||||
#import <Security/Security.h>
|
||||
|
||||
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
|
||||
@interface SFHFKeychainUtils (PrivateMethods)
|
||||
+ (SecKeychainItemRef)getKeychainItemReferenceForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error;
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation SFHFKeychainUtils
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
|
||||
|
||||
+ (NSString *)getPasswordForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !serviceName)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
|
||||
andServerName:serverName
|
||||
error:error];
|
||||
|
||||
if (*error || !item)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// from Advanced Mac OS X Programming, ch. 16
|
||||
UInt32 length;
|
||||
char *password;
|
||||
SecKeychainAttribute attributes[8];
|
||||
SecKeychainAttributeList list;
|
||||
|
||||
attributes[0].tag = kSecAccountItemAttr;
|
||||
attributes[1].tag = kSecDescriptionItemAttr;
|
||||
attributes[2].tag = kSecLabelItemAttr;
|
||||
attributes[3].tag = kSecModDateItemAttr;
|
||||
|
||||
list.count = 4;
|
||||
list.attr = attributes;
|
||||
|
||||
OSStatus status = SecKeychainItemCopyContent(item, nullptr, &list, &length, (void **)&password);
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *passwordString = nil;
|
||||
|
||||
if (password != nullptr)
|
||||
{
|
||||
char passwordBuffer[1024];
|
||||
|
||||
if (length > 1023)
|
||||
{
|
||||
length = 1023;
|
||||
}
|
||||
strncpy(passwordBuffer, password, length);
|
||||
|
||||
passwordBuffer[length] = '\0';
|
||||
passwordString = [NSString stringWithCString:passwordBuffer encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
SecKeychainItemFreeContent(&list, password);
|
||||
|
||||
CFRelease(item);
|
||||
|
||||
return passwordString;
|
||||
}
|
||||
|
||||
+ (void)storeUsername:(NSString *)username
|
||||
andPassword:(NSString *)password
|
||||
forServerName:(NSString *)serverName
|
||||
updateExisting:(BOOL)updateExisting
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !password || !serverName)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
OSStatus status = noErr;
|
||||
|
||||
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
|
||||
andServerName:serverName
|
||||
error:error];
|
||||
|
||||
if (*error && [*error code] != noErr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*error = nil;
|
||||
|
||||
if (item)
|
||||
{
|
||||
status = SecKeychainItemModifyAttributesAndData(
|
||||
item, nullptr, strlen([password UTF8String]), [password UTF8String]);
|
||||
|
||||
CFRelease(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = SecKeychainAddGenericPassword(
|
||||
nullptr, strlen([serverName UTF8String]), [serverName UTF8String],
|
||||
strlen([username UTF8String]), [username UTF8String], strlen([password UTF8String]),
|
||||
[password UTF8String], nullptr);
|
||||
}
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)deleteItemForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !serverName)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:2000 userInfo:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
*error = nil;
|
||||
|
||||
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
|
||||
andServerName:serverName
|
||||
error:error];
|
||||
|
||||
if (*error && [*error code] != noErr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OSStatus status;
|
||||
|
||||
if (item)
|
||||
{
|
||||
status = SecKeychainItemDelete(item);
|
||||
|
||||
CFRelease(item);
|
||||
}
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
+ (SecKeychainItemRef)getKeychainItemReferenceForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !serverName)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
*error = nil;
|
||||
|
||||
SecKeychainItemRef item;
|
||||
|
||||
OSStatus status = SecKeychainFindGenericPassword(
|
||||
nullptr, strlen([serverName UTF8String]), [serverName UTF8String],
|
||||
strlen([username UTF8String]), [username UTF8String], nullptr, nullptr, &item);
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
if (status != errSecItemNotFound)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
+ (NSString *)getPasswordForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !serverName)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (error != nil)
|
||||
{
|
||||
*error = nil;
|
||||
}
|
||||
|
||||
// Set up a query dictionary with the base query attributes: item type (generic), username, and
|
||||
// service
|
||||
|
||||
NSArray *keys = [[[NSArray alloc]
|
||||
initWithObjects:(NSString *)kSecClass, kSecAttrAccount, kSecAttrService, nil] autorelease];
|
||||
NSArray *objects = [[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword,
|
||||
username, serverName, nil] autorelease];
|
||||
|
||||
NSMutableDictionary *query = [[[NSMutableDictionary alloc] initWithObjects:objects
|
||||
forKeys:keys] autorelease];
|
||||
|
||||
// First do a query for attributes, in case we already have a Keychain item with no password
|
||||
// data set. One likely way such an incorrect item could have come about is due to the previous
|
||||
// (incorrect) version of this code (which set the password as a generic attribute instead of
|
||||
// password data).
|
||||
|
||||
NSDictionary *attributeResult = nullptr;
|
||||
NSMutableDictionary *attributeQuery = [query mutableCopy];
|
||||
[attributeQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
|
||||
OSStatus status =
|
||||
SecItemCopyMatching((CFDictionaryRef)attributeQuery, (CFTypeRef *)&attributeResult);
|
||||
|
||||
[attributeResult release];
|
||||
[attributeQuery release];
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
// No existing item found--simply return nil for the password
|
||||
if (error != nil && status != errSecItemNotFound)
|
||||
{
|
||||
// Only return an error if a real exception happened--not simply for "not found."
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// We have an existing item, now query for the password data associated with it.
|
||||
|
||||
NSData *resultData = nil;
|
||||
NSMutableDictionary *passwordQuery = [query mutableCopy];
|
||||
[passwordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
|
||||
|
||||
status = SecItemCopyMatching((CFDictionaryRef)passwordQuery, (CFTypeRef *)&resultData);
|
||||
|
||||
[resultData autorelease];
|
||||
[passwordQuery release];
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
if (status == errSecItemNotFound)
|
||||
{
|
||||
// We found attributes for the item previously, but no password now, so return a special
|
||||
// error. Users of this API will probably want to detect this error and prompt the user
|
||||
// to re-enter their credentials. When you attempt to store the re-entered credentials
|
||||
// using storeUsername:andPassword:forServiceName:updateExisting:error
|
||||
// the old, incorrect entry will be deleted and a new one with a properly encrypted
|
||||
// password will be added.
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
|
||||
code:-1999
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something else went wrong. Simply return the normal Keychain API error code.
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *password = nil;
|
||||
|
||||
if (resultData)
|
||||
{
|
||||
password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is an existing item, but we weren't able to get password data for it for some
|
||||
// reason, Possibly as a result of an item being incorrectly entered by the previous code.
|
||||
// Set the -1999 error so the code above us can prompt the user again.
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-1999 userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
return [password autorelease];
|
||||
}
|
||||
|
||||
+ (BOOL)storeUsername:(NSString *)username
|
||||
andPassword:(NSString *)password
|
||||
forServerName:(NSString *)serverName
|
||||
updateExisting:(BOOL)updateExisting
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !password || !serverName)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// See if we already have a password entered for these credentials.
|
||||
NSError *getError = nil;
|
||||
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername:username
|
||||
andServerName:serverName
|
||||
error:&getError];
|
||||
|
||||
if ([getError code] == -1999)
|
||||
{
|
||||
// There is an existing entry without a password properly stored (possibly as a result of
|
||||
// the previous incorrect version of this code. Delete the existing item before moving on
|
||||
// entering a correct one.
|
||||
|
||||
getError = nil;
|
||||
|
||||
[self deleteItemForUsername:username andServerName:serverName error:&getError];
|
||||
|
||||
if ([getError code] != noErr)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
*error = getError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else if ([getError code] != noErr)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
*error = getError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (error != nil)
|
||||
{
|
||||
*error = nil;
|
||||
}
|
||||
|
||||
OSStatus status = noErr;
|
||||
|
||||
if (existingPassword)
|
||||
{
|
||||
// We have an existing, properly entered item with a password.
|
||||
// Update the existing item.
|
||||
|
||||
if (![existingPassword isEqualToString:password] && updateExisting)
|
||||
{
|
||||
// Only update if we're allowed to update existing. If not, simply do nothing.
|
||||
|
||||
NSArray *keys =
|
||||
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrService,
|
||||
kSecAttrLabel, kSecAttrAccount, nil] autorelease];
|
||||
|
||||
NSArray *objects =
|
||||
[[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword, serverName,
|
||||
serverName, username, nil] autorelease];
|
||||
|
||||
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects
|
||||
forKeys:keys] autorelease];
|
||||
|
||||
status = SecItemUpdate(
|
||||
(CFDictionaryRef)query,
|
||||
(CFDictionaryRef)[NSDictionary
|
||||
dictionaryWithObject:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
forKey:(NSString *)kSecValueData]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No existing entry (or an existing, improperly entered, and therefore now
|
||||
// deleted, entry). Create a new entry.
|
||||
|
||||
NSArray *keys =
|
||||
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrService, kSecAttrLabel,
|
||||
kSecAttrAccount, kSecValueData, nil] autorelease];
|
||||
|
||||
NSArray *objects = [[[NSArray alloc]
|
||||
initWithObjects:(NSString *)kSecClassGenericPassword, serverName, serverName, username,
|
||||
[password dataUsingEncoding:NSUTF8StringEncoding], nil] autorelease];
|
||||
|
||||
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects
|
||||
forKeys:keys] autorelease];
|
||||
|
||||
status = SecItemAdd((CFDictionaryRef)query, nullptr);
|
||||
}
|
||||
|
||||
if (error != nil && status != noErr)
|
||||
{
|
||||
// Something went wrong with adding the new item. Return the Keychain error code.
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)deleteItemForUsername:(NSString *)username
|
||||
andServerName:(NSString *)serverName
|
||||
error:(NSError **)error
|
||||
{
|
||||
if (!username || !serverName)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (error != nil)
|
||||
{
|
||||
*error = nil;
|
||||
}
|
||||
|
||||
NSArray *keys =
|
||||
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrAccount, kSecAttrService,
|
||||
kSecReturnAttributes, nil] autorelease];
|
||||
NSArray *objects =
|
||||
[[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword, username,
|
||||
serverName, kCFBooleanTrue, nil] autorelease];
|
||||
|
||||
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects forKeys:keys] autorelease];
|
||||
|
||||
OSStatus status = SecItemDelete((CFDictionaryRef)query);
|
||||
|
||||
if (error != nil && status != noErr)
|
||||
{
|
||||
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user