Last active
June 6, 2017 16:13
-
-
Save adil-hussain-84/0b74fc7b34f3df11b125d3e1cb1b8257 to your computer and use it in GitHub Desktop.
Helper class which adds/gets/deletes RSA public keys in the iOS Keychain for signature verification.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@interface MJRSecKeyRefProvider : NSObject | |
/** | |
* @param tag the identifier with which to save keys in the Keychain. | |
*/ | |
- (instancetype)initWithTag:(NSString *)tag; | |
/** | |
* Saves the provided public key in the Keychain | |
* and provides a SecKeyRef object that links to it. | |
* | |
* @param publicKeyData the encoded public key. | |
* @param keySizeInBits the size of the key in bits (i.e. 2048, 4096 etc). | |
* @return a SecKeyRef object for the key after saving the key in the keychain, or nil in case of error. | |
*/ | |
- (SecKeyRef)provideForPublicKeyData:(NSData *)publicKeyData | |
keySizeInBits:(NSNumber *)keySizeInBits; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "MJRSecKeyRefProvider.h" | |
@implementation MJRSecKeyRefProvider { | |
NSString *_tag; | |
} | |
- (instancetype)initWithTag:(NSString *)tag { | |
if (self = [super init]) { | |
_tag = tag; | |
} | |
return self; | |
} | |
- (SecKeyRef)provideForPublicKeyData:(NSData *)publicKeyData | |
keySizeInBits:(NSNumber *)keySizeInBits { | |
BOOL deletePublicKeySuccess = [self deletePublicKey]; | |
if (!deletePublicKeySuccess) { | |
return nil; | |
} | |
BOOL savePublicKeySuccess = [self savePublicKeyData:publicKeyData | |
keySizeInBits:keySizeInBits]; | |
if (!savePublicKeySuccess) { | |
return nil; | |
} | |
return [self getPublicKey]; | |
} | |
- (BOOL)savePublicKeyData:(NSData *)publicKeyData | |
keySizeInBits:(NSNumber *)keySizeInBits { | |
NSMutableDictionary *query = [self getQuery]; | |
query[(__bridge id) kSecAttrEffectiveKeySize] = keySizeInBits; | |
query[(__bridge id) kSecAttrKeySizeInBits] = keySizeInBits; | |
query[(__bridge id) kSecAttrCanDecrypt] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecAttrCanDerive] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecAttrCanEncrypt] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecAttrCanSign] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecAttrCanVerify] = (__bridge id) kCFBooleanTrue; | |
query[(__bridge id) kSecAttrCanUnwrap] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecAttrCanWrap] = (__bridge id) kCFBooleanFalse; | |
query[(__bridge id) kSecValueData] = [self stripPublicKeyHeader:publicKeyData]; | |
SecKeyRef secKeyRef = NULL; | |
OSStatus result = SecItemAdd((__bridge CFDictionaryRef) query, (CFTypeRef *) &secKeyRef); | |
return result == errSecSuccess; | |
} | |
- (SecKeyRef)getPublicKey { | |
NSMutableDictionary *query = [self getQuery]; | |
query[(__bridge id) kSecReturnRef] = (__bridge id) kCFBooleanTrue; | |
SecKeyRef secKeyRef = NULL; | |
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef *) &secKeyRef); | |
if (status != errSecSuccess) { | |
return nil; | |
} | |
return secKeyRef; | |
} | |
- (BOOL)deletePublicKey { | |
NSMutableDictionary *query = [self getQuery]; | |
OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); | |
return (status == errSecSuccess) || (status == errSecItemNotFound); | |
} | |
#pragma mark - Private methods | |
- (NSMutableDictionary *)getQuery { | |
NSData *tagData = [_tag dataUsingEncoding:NSUTF8StringEncoding]; | |
NSMutableDictionary *query = [NSMutableDictionary dictionary]; | |
query[(__bridge id) kSecAttrApplicationTag] = tagData; | |
query[(__bridge id) kSecAttrKeyClass] = (__bridge id) kSecAttrKeyClassPublic; | |
query[(__bridge id) kSecAttrKeyType] = (__bridge id) kSecAttrKeyTypeRSA; | |
query[(__bridge id) kSecClass] = (__bridge id) kSecClassKey; | |
return query; | |
} | |
/** | |
* This method exists to strip the ASN.1 SubjectPublicKeyInfo header if present in the key. | |
* iOS 9 silently fails to import a key with this structure | |
* and does not return a SecKeyRef to the key in question. | |
* | |
* For reference, see the below: | |
* https://forums.developer.apple.com/thread/13748 | |
* https://forums.developer.apple.com/thread/15129 | |
* http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/ | |
*/ | |
- (NSData *)stripPublicKeyHeader:(NSData *)d_key { | |
if (d_key == nil) return nil; | |
unsigned int len = (unsigned int) [d_key length]; | |
if (!len) return nil; | |
unsigned char *c_key = (unsigned char *) [d_key bytes]; | |
unsigned int idx = 0; | |
if (c_key[idx++] != 0x30) return nil; | |
if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; | |
else idx++; | |
// PKCS #1 rsaEncryption szOID_RSA_RSA | |
static unsigned char seqiod[] = { | |
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, | |
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00 | |
}; | |
if (memcmp(&c_key[idx], seqiod, 15)) return nil; | |
idx += 15; | |
if (c_key[idx++] != 0x03) return nil; | |
if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; | |
else idx++; | |
if (c_key[idx++] != '\0') return nil; | |
return [NSData dataWithBytes:&c_key[idx] length:len - idx]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment