Skip to content

Instantly share code, notes, and snippets.

@rbaulin
Last active April 25, 2017 11:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbaulin/26a31dc30a6ebe78560e3299d8f41d29 to your computer and use it in GitHub Desktop.
Save rbaulin/26a31dc30a6ebe78560e3299d8f41d29 to your computer and use it in GitHub Desktop.
//
// KeychainBehaviourTests.m
//
// Created by Roman Baulin on 25/04/2017.
// Copyright © 2017. All rights reserved.
//
//
// see SecBase.h and osstatus.com and to investigate keychain error codes
//
// errSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
// errSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
// errSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
// errSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
//
#import <XCTest/XCTest.h>
@interface KeychainBehaviourTests : XCTestCase
@end
@implementation KeychainBehaviourTests
- (void)setUp {
[super setUp];
// cleanup keychain
NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
- (void)tearDown {
// cleanup keychain
NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
[super tearDown];
}
- (void)testKeychainAddAccessibleNilSearchAllCases {
// this test shows that kSecAttrAccessible defaults to kSecAttrAccessibleWhenUnlocked
NSString *service = @"service";
NSString *account = @"account";
NSData *data = [@"whenunlocked" dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status;
NSDictionary *query;
CFMutableDictionaryRef result = nil;
// add item with kSecAttrAccessible nil
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked };
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
// fetch, double check
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess, @"fetch kSecAttrAccessible nil");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenUnlocked");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAfterFirstUnlock");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAlways");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenUnlockedThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAlwaysThisDeviceOnly");
}
- (void)testKeychainAddAccessibleAlwaysSearchAllCases {
NSString *service = @"service";
NSString *account = @"account";
NSData *data = [@"always" dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status;
NSDictionary *query;
CFMutableDictionaryRef result = nil;
// add item with kSecAttrAccessible nil
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
// fetch, double check
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess, @"fetch kSecAttrAccessible nil");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess, @"fetch kSecAttrAccessible kSecAttrAccessibleAlways");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenUnlocked");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAfterFirstUnlock");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleWhenUnlockedThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly");
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecItemNotFound, @"fetch kSecAttrAccessible kSecAttrAccessibleAlwaysThisDeviceOnly");
}
- (void)testKeychainAdd {
// result: add results in errSecDuplicateItem when trying to add with different kSecAttrAccessible attribute
// NOTE: in case of background use kSecAttrAccessibleAfterFirstUnlock or kSecAttrAccessibleAlways in SecItemAdd
// otherwise default kSecAttrAccessible is kSecAttrAccessibleWhenUnlocked, which leads to -25308 errSecInteractionNotAllowed error
NSString *service = @"service";
NSString *account = @"account";
NSData *data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status;
NSDictionary *query;
CFMutableDictionaryRef result = nil;
// add item
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked };
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecDuplicateItem);
// add item with different kSecAttrAccessible attribute
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock };
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecDuplicateItem);
}
- (void)testKeychainUpdate {
// result: can update item added with different kSecAttrAccessible attribute
// do not set kSecAttrAccessible attribute for SecItemCopyMatching
NSString *service = @"service";
NSString *account = @"account";
OSStatus status;
NSDictionary *query;
CFMutableDictionaryRef result = nil;
// add
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: [@"data" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
// find item with * kSecAttrAccessible
NSDictionary *searchQuery = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account };
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
// update found item with new data and kSecAttrAccessible attribute
NSDictionary *updateQuery = @{ (__bridge id)kSecValueData: [@"update" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly };
status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(updateQuery));
XCTAssert(status == errSecSuccess);
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
// update found item with new kSecAttrAccessible attribute
updateQuery = @{ (__bridge id)kSecValueData: [@"update" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleAlways };
status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(updateQuery));
XCTAssert(status == errSecSuccess);
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, (CFTypeRef *)&result);
XCTAssert(status == errSecSuccess);
}
- (void)testKeychainCopy {
// result: can not fetch items added with different kSecAttrAccessible attribute
// same as in search, do not set kSecAttrAccessible for SecItemCopyMatching
NSString *service = @"service";
NSString *account = @"account";
NSString *inString = @"data";
OSStatus status;
NSDictionary *query;
CFTypeRef result;
// add
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: [inString dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
XCTAssert(status == errSecSuccess);
// search with different kSecAttrAccessible
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
XCTAssert(status == errSecItemNotFound);
// search with * kSecAttrAccessible
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne };
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
XCTAssert(status == errSecSuccess);
NSData *passwordData = (__bridge_transfer NSData *)result;
NSString *outString = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(outString, inString);
}
- (void)testKeychainDelete {
// result: can not delete items added with different kSecAttrAccessible attribute
// dont use kSecAttrAccessible when deleting
NSString *service = @"service";
NSString *account = @"account";
OSStatus status;
NSDictionary *query;
// add
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: [@"data" dataUsingEncoding:NSUTF8StringEncoding], };
//(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock };
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
XCTAssert(status == errSecSuccess);
// delete with specific kSecAttrAccessible
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAlways };
status = SecItemDelete((__bridge CFDictionaryRef)query);
XCTAssert(status == errSecItemNotFound);
// delete with * kSecAttrAccessible
query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account };
status = SecItemDelete((__bridge CFDictionaryRef)query);
XCTAssert(status == errSecSuccess);
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment