Sources/JWT/Algorithms/RSFamily/RSKeys/JWTCryptoSecurity.m
//
// JWTCryptoSecurity.m
// JWT
//
// Created by Lobanov Dmitry on 04.02.17.
// Copyright © 2017 JWTIO. All rights reserved.
//
#import "JWTCryptoSecurity.h"
#import "JWTCryptoSecurity+ErrorHandling.h"
#import "JWTErrorDescription.h"
#import "JWTDeprecations.h"
@interface JWTMemoryLayout : NSObject
+ (NSString *)typeUInt8;
+ (NSString *)typeCUnsignedChar;
- (instancetype)initWithType:(NSString *)type;
+ (instancetype)createWithType:(NSString *)type;
@property (copy, nonatomic, readwrite) NSString *type;
@property (assign, nonatomic, readonly) NSInteger size;
@end
@implementation JWTMemoryLayout
+ (NSString *)typeUInt8 {
return NSStringFromSelector(_cmd);
}
+ (NSString *)typeCUnsignedChar {
return [self typeUInt8];
}
- (instancetype)initWithType:(NSString *)type {
self = [super init];
if (self) {
self.type = type;
}
return self;
}
+ (instancetype)createWithType:(NSString *)type {
return [[self alloc] initWithType:type];
}
+ (NSDictionary *)sizesAndTypes {
static NSDictionary *sizesAndTypes = nil;
return sizesAndTypes ?: (sizesAndTypes = @{
[self typeUInt8] : @(1) // or 8?
});
}
- (NSInteger)size {
return [[self.class sizesAndTypes][self.type] integerValue];
}
@end
@implementation JWTCryptoSecurityKeysTypes
+ (NSString *)RSA {
return (__bridge NSString *)kSecAttrKeyTypeRSA;
}
+ (NSString *)EC {
// extern const CFStringRef kSecAttrKeyTypeEC
// __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_4_0);
// extern const CFStringRef kSecAttrKeyTypeECSECPrimeRandom
// __OSX_AVAILABLE_STARTING(__MAC_10_12, __IPHONE_10_0);
return (__bridge NSString *)kSecAttrKeyTypeEC;
}
@end
@interface JWTCryptoSecurity ()
+ (NSDictionary *)dictionaryByCombiningDictionaries:(NSArray *)dictionaries;
@end
@implementation JWTCryptoSecurity (KeysManipulation)
+ (SecKeyRef)addKeyWithData:(NSData *)data asPublic:(BOOL)thePublic tag:(NSString *)tag type:(NSString *)type error:(NSError *__autoreleasing*)error; {
NSString *keyClass = (__bridge NSString *)(thePublic ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate);
NSInteger sizeInBits = data.length * [JWTMemoryLayout createWithType:[JWTMemoryLayout typeUInt8]].size;
NSDictionary *attributes = @{
(__bridge NSString*)kSecAttrKeyType : type,
(__bridge NSString*)kSecAttrKeyClass : keyClass,
(__bridge NSString*)kSecAttrKeySizeInBits : @(sizeInBits)
};
if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
CFErrorRef createError = NULL;
SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)attributes, &createError);
if (error && createError != NULL) {
*error = (__bridge NSError*)createError;
}
return key;
}
// oh... not avaialbe API :/
else {
CFTypeRef result = NULL;
NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *commonAttributes = @{
(__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassKey,
(__bridge NSString*)kSecAttrApplicationTag: tagData,
(__bridge NSString*)kSecAttrAccessible: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked
};
NSDictionary *addItemAttributes = @{
(__bridge NSString*)kSecValueData: data,
(__bridge NSString*)kSecReturnPersistentRef: @(YES),
};
OSStatus addItemStatus = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryByCombiningDictionaries:@[attributes, commonAttributes, addItemAttributes]], &result);
if (addItemStatus != errSecSuccess && addItemStatus != errSecDuplicateItem) {
// add item error
// not duplicate and not added to keychain.
return NULL;
}
NSDictionary *copyAttributes = @{
(__bridge NSString*)kSecReturnRef: @(YES),
};
CFTypeRef key = NULL;
// TODO: Add error handling later.
OSStatus copyItemStatus = SecItemCopyMatching((__bridge CFDictionaryRef)[self dictionaryByCombiningDictionaries:@[attributes, commonAttributes, copyAttributes]], &key);
if (key == NULL) {
// copy item error
if (error) {
*error = [JWTCryptoSecurity securityErrorWithOSStatus:copyItemStatus];
}
}
return (SecKeyRef)key;
}
return NULL;
}
+ (SecKeyRef)addKeyWithData:(NSData *)data asPublic:(BOOL)public tag:(NSString *)tag error:(NSError *__autoreleasing*)error; {
return [self addKeyWithData:data asPublic:public tag:tag type:JWTCryptoSecurityKeysTypes.RSA error:error];
}
+ (SecKeyRef)keyByTag:(NSString *)tag error:(NSError *__autoreleasing*)error; {
return NULL;
}
+ (BOOL)removeKeyByTag:(NSString *)tag error:(NSError *__autoreleasing*)error; {
NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
if (tagData == nil) {
// tell that nothing to remove.
if (error) {
*error = [JWTErrorDescription errorWithCode:JWTUnexpectedError];
}
return NO;
}
NSDictionary *removeAttributes = @{
(__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassKey,
(__bridge NSString*)kSecAttrKeyType: (__bridge NSString*)kSecAttrKeyTypeRSA,
(__bridge NSString*)kSecAttrApplicationTag: tagData
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)removeAttributes);
if (status != errSecSuccess) {
if (error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
}
}
return status != errSecSuccess;
}
@end
@implementation JWTCryptoSecurity
+ (NSDictionary *)dictionaryByCombiningDictionaries:(NSArray *)dictionaries {
NSMutableDictionary *result = [@{} mutableCopy];
for (NSDictionary *dictionary in dictionaries) {
[result addEntriesFromDictionary:dictionary];
}
return [result copy];
}
+ (NSString *)keyTypeRSA {
return JWTCryptoSecurityKeysTypes.RSA;
}
+ (NSString *)keyTypeEC {
return JWTCryptoSecurityKeysTypes.EC;
}
@end
@implementation JWTCryptoSecurity (Certificates)
+ (OSStatus)extractIdentityAndTrustFromPKCS12:(CFDataRef)inPKCS12Data password:(CFStringRef)password identity:(SecIdentityRef *)outIdentity trust:(SecTrustRef *)outTrust {
return [self extractIdentityAndTrustFromPKCS12:inPKCS12Data password:password identity:outIdentity trust:outTrust error:NULL];
}
+ (OSStatus)extractIdentityAndTrustFromPKCS12:(CFDataRef)inPKCS12Data password:(CFStringRef)password identity:(SecIdentityRef *)outIdentity trust:(SecTrustRef *)outTrust error:(CFErrorRef *)error {
OSStatus securityError = errSecSuccess;
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef optionsDictionary = NULL;
/* Create a dictionary containing the passphrase if one
was specified. Otherwise, create an empty dictionary. */
optionsDictionary = CFDictionaryCreate(
NULL, keys,
values, (password ? 1 : 0),
NULL, NULL); // 1
CFArrayRef items = NULL;
securityError = SecPKCS12Import(inPKCS12Data,
optionsDictionary,
&items); // 2
/**
@discussion
If a pkcs12 that was created with only one private key in it and no certificate was tried used, this just crashed when accessing index 0 with the CFArrayGetValueAtIndex.
*/
if (items != nil && CFArrayGetCount(items) == 0) {
securityError = errSecPkcs12VerifyFailure;
}
//
if (securityError == 0) { // 3
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
kSecImportItemIdentity);
CFRetain(tempIdentity);
*outIdentity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
CFRetain(tempTrust);
*outTrust = (SecTrustRef)tempTrust;
}
else {
if (error) {
NSError *resultError = [JWTCryptoSecurity securityErrorWithOSStatus:securityError];
CFErrorRef theError = (CFErrorRef)CFBridgingRetain(resultError);
*error = theError;
}
}
if (optionsDictionary) {
CFRelease(optionsDictionary);
}
if (items) {
CFRelease(items);
}
return securityError;
}
+ (SecKeyRef)trustCopyKey:(SecTrustRef)trust {
// API_DEPRECATED_WITH_REPLACEMENT("SecTrustCopyKey", macos(10.7, 11.0), ios(2.0, 14.0), watchos(1.0, 7.0), tvos(9.0, 14.0));
#if JWT_COMPILE_TIME_AVAILABILITY(JWT_macOS(110000), JWT_iOS(140000), JWT_tvOS(140000), JWT_watchOS(70000))
return SecTrustCopyKey(trust);
#else
return SecTrustCopyPublicKey(trust);
#endif
}
+ (SecKeyRef)publicKeyFromCertificate:(NSData *)certificateData {
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData);
if (certificate != NULL) {
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates(certificate, secPolicy, &trust);
// if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) {
#if JWT_COMPILE_TIME_AVAILABILITY(JWT_macOS(101400), JWT_iOS(120000), JWT_tvOS(120000), JWT_watchOS(50000))
CFErrorRef errorRef = NULL;
BOOL result = SecTrustEvaluateWithError(trust, &errorRef);
if (result == NO) {
NSError *error = (__bridge NSError *)errorRef;
NSLog(@"%s Error occured while retrieving public key from : %@", __PRETTY_FUNCTION__, error);
if (errorRef != NULL) {
CFRelease(errorRef);
}
}
#else
SecTrustResultType resultType;
SecTrustEvaluate(trust, &resultType);
#endif
SecKeyRef publicKey = [self trustCopyKey:trust];
(CFRelease(trust));
(CFRelease(secPolicy));
(CFRelease(certificate));
return publicKey;
}
return NULL;
}
@end
@implementation JWTCryptoSecurity (PublicKey)
/**
This method strips the x509 from a provided ASN.1 DER public key.
If the key doesn't contain a header, the DER data is returned as is.
Supported formats are:
Headerless:
SEQUENCE
INTEGER (1024 or 2048 bit) -- modulo
INTEGER -- public exponent
With x509 header:
SEQUENCE
SEQUENCE
OBJECT IDENTIFIER 1.2.840.113549.1.1.1
NULL
BIT STRING
SEQUENCE
INTEGER (1024 or 2048 bit) -- modulo
INTEGER -- public exponent
Example of headerless key:
https://lapo.it/asn1js/#3082010A0282010100C1A0DFA367FBC2A5FD6ED5A071E02A4B0617E19C6B5AD11BB61192E78D212F10A7620084A3CED660894134D4E475BAD7786FA1D40878683FD1B7A1AD9C0542B7A666457A270159DAC40CE25B2EAE7CCD807D31AE725CA394F90FBB5C5BA500545B99C545A9FE08EFF00A5F23457633E1DB84ED5E908EF748A90F8DFCCAFF319CB0334705EA012AF15AA090D17A9330159C9AFC9275C610BB9B7C61317876DC7386C723885C100F774C19830F475AD1E9A9925F9CA9A69CE0181A214DF2EB75FD13E6A546B8C8ED699E33A8521242B7E42711066AEC22D25DD45D56F94D3170D6F2C25164D2DACED31C73963BA885ADCB706F40866B8266433ED5161DC50E4B3B0203010001
Example of key with X509 header (notice the additional ASN.1 sequence):
https://lapo.it/asn1js/#30819F300D06092A864886F70D010101050003818D0030818902818100D0674615A252ED3D75D2A3073A0A8A445F3188FD3BEB8BA8584F7299E391BDEC3427F287327414174997D147DD8CA62647427D73C9DA5504E0A3EED5274A1D50A1237D688486FADB8B82061675ABFA5E55B624095DB8790C6DBCAE83D6A8588C9A6635D7CF257ED1EDE18F04217D37908FD0CBB86B2C58D5F762E6207FF7B92D0203010001
*/
//static func stripPublicKeyHeader(keyData: Data) throws -> Data {
// let count = keyData.count / MemoryLayout<CUnsignedChar>.size
//
// guard count > 0 else {
// throw SwiftyRSAError(message: "Provided public key is empty")
// }
//
// var byteArray = [UInt8](repeating: 0, count: count)
// (keyData as NSData).getBytes(&byteArray, length: keyData.count)
//
// var index = 0
// guard byteArray[index] == 0x30 else {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid ASN.1 structure (first byte should be 0x30 == SEQUENCE)")
// }
//
// index += 1
// if byteArray[index] > 0x80 {
// index += Int(byteArray[index]) - 0x80 + 1
// } else {
// index += 1
// }
//
// // If current byte marks an integer (0x02), it means the key doesn't have a X509 header and just
// // contains its modulo & public exponent. In this case, we can just return the provided DER data as is.
// if Int(byteArray[index]) == 0x02 {
// return keyData
// }
//
// // Now that we've excluded the possibility of headerless key, we're looking for a valid X509 header sequence.
// // It should look like this:
// // 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00
// guard Int(byteArray[index]) == 0x30 else {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid X509 header")
// }
//
// index += 15
// if byteArray[index] != 0x03 {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
// }
//
// index += 1
// if byteArray[index] > 0x80 {
// index += Int(byteArray[index]) - 0x80 + 1
// } else {
// index += 1
// }
//
// guard byteArray[index] == 0 else {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
// }
//
// index += 1
//
// let strippedKeyBytes = [UInt8](byteArray[index...keyData.count - 1])
// let data = Data(bytes: UnsafePointer<UInt8>(strippedKeyBytes), count: keyData.count - index)
//
// return data
//}
typedef NS_ENUM(NSInteger, JWTPublicHeaderStrippingError) {
JWTPublicHeaderStrippingError_KeyIsEmpty = -200,
JWTPublicHeaderStrippingError_Invalid_ASN1_Structure,
JWTPublicHeaderStrippingError_Invalid_X509_Header,
JWTPublicHeaderStrippingError_Invalid_Byte_At_Index,
};
+ (NSDictionary *)publicHeaderStrippingMessagesAndCodes {
static NSDictionary *publicHeaderStrippingMessagesAndCodes = nil;
return publicHeaderStrippingMessagesAndCodes ?:
(publicHeaderStrippingMessagesAndCodes = @{
@(JWTPublicHeaderStrippingError_KeyIsEmpty) : @"Provided public key is empty",
@(JWTPublicHeaderStrippingError_Invalid_ASN1_Structure) : @"Provided key doesn't have a valid ASN.1 structure (first byte should be 0x30 == SEQUENCE)",
@(JWTPublicHeaderStrippingError_Invalid_X509_Header) : @"Provided key doesn't have a valid X509 header",
@(JWTPublicHeaderStrippingError_Invalid_Byte_At_Index) : @"Invalid byte at index (index - 1) ((bytes[index - 1])) for public key header. Access as error.userInfo[Parameters][index] or error.userInfo[Parameters][byte]"
});
}
+ (NSString *)stringForPublicHeaderStrippingErrorCode:(NSInteger)code {
return [self publicHeaderStrippingMessagesAndCodes][@(code)] ?: @"Unknown Public Header Stripping Error";
}
+ (NSError*)publicHeaderStrippingErrorForCode:(NSInteger)code parameters:(NSDictionary *)parameters {
return [NSError errorWithDomain:@"io.jwt.crypto.stripping_public_header" code:code userInfo:@{
@"Parameters" : parameters ?: [NSNull null],NSLocalizedDescriptionKey: [self stringForPublicHeaderStrippingErrorCode:code]
}];
}
+ (NSError*)publicHeaderStrippingErrorForCode:(NSInteger)code {
return [self publicHeaderStrippingErrorForCode:code parameters:nil];
}
+ (NSData *)dataByRemovingPublicKeyHeader:(NSData *)data error:(NSError *__autoreleasing *)error {
NSError *currentError = nil;
NSData *currentData = [data copy];
// let count = keyData.count / MemoryLayout<CUnsignedChar>.size
//
// guard count > 0 else {
// throw SwiftyRSAError(message: "Provided public key is empty")
// }
NSInteger countOfBytes = currentData.length / [JWTMemoryLayout createWithType:[JWTMemoryLayout typeUInt8]].size;
if (countOfBytes == 0) {
// throw SwiftyRSAError(message: "Provided public key is empty")
currentError = [self publicHeaderStrippingErrorForCode:JWTPublicHeaderStrippingError_KeyIsEmpty];
if (error) {
*error = currentError;
}
return nil;
}
// var byteArray = [UInt8](repeating: 0, count: count)
// (keyData as NSData).getBytes(&byteArray, length: keyData.count)
// UInt8 *bytes = (UInt8*)malloc(countOfBytes);
UInt8 *bytes = (UInt8 *)[currentData bytes];
// memcpy(bytes, [currentData bytes], countOfBytes);
// var index = 0
// guard byteArray[index] == 0x30 else {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid ASN.1 structure (first byte should be 0x30 == SEQUENCE)")
// }
UInt8 index = 0;
if (bytes[index] != 0x30) {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid ASN.1 structure (first byte should be 0x30 == SEQUENCE)")
currentError = [self publicHeaderStrippingErrorForCode:JWTPublicHeaderStrippingError_Invalid_ASN1_Structure];
if (error) {
*error = currentError;
}
return nil;
}
// index += 1
// if byteArray[index] > 0x80 {
// index += Int(byteArray[index]) - 0x80 + 1
// } else {
// index += 1
// }
index += 1;
if (bytes[index] > 0x80) {
index += (SInt8)bytes[index] - 0x80 + 1;
}
else {
index += 1;
}
// Headerless!
// // If current byte marks an integer (0x02), it means the key doesn't have a X509 header and just
// // contains its modulo & public exponent. In this case, we can just return the provided DER data as is.
// if Int(byteArray[index]) == 0x02 {
// return keyData
// }
if ((SInt8)bytes[index] == 0x02) {
return currentData;
}
// Has header.
// // Now that we've excluded the possibility of headerless key, we're looking for a valid X509 header sequence.
// // It should look like this:
// // 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00
// guard Int(byteArray[index]) == 0x30 else {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid X509 header")
// }
if ((SInt8)bytes[index] != 0x30) {
// throw SwiftyRSAError(message: "Provided key doesn't have a valid X509 header")
currentError = [self publicHeaderStrippingErrorForCode:JWTPublicHeaderStrippingError_Invalid_X509_Header];
if (error) {
*error = currentError;
}
return nil;
}
// index += 15
// if byteArray[index] != 0x03 {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
// }
index += 15;
if (bytes[index] != 0x03) {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
currentError = [self publicHeaderStrippingErrorForCode:JWTPublicHeaderStrippingError_Invalid_Byte_At_Index parameters:@{@"index" : @(index - 1), @"byte": @(bytes[index - 1] ?: 0)}];
if (error) {
*error = currentError;
}
return nil;
}
// index += 1
// if byteArray[index] > 0x80 {
// index += Int(byteArray[index]) - 0x80 + 1
// } else {
// index += 1
// }
index += 1;
if (bytes[index] > 0x80) {
index += (SInt8)bytes[index] - 0x80 + 1;
}
else {
index += 1;
}
// guard byteArray[index] == 0 else {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
// }
if (bytes[index] != 0) {
// throw SwiftyRSAError(message: "Invalid byte at index \(index - 1) (\(byteArray[index - 1])) for public key header")
currentError = [self publicHeaderStrippingErrorForCode:JWTPublicHeaderStrippingError_Invalid_Byte_At_Index parameters:@{@"index" : @(index - 1), @"byte": @(bytes[index - 1] ?: 0)}];
if (error) {
*error = currentError;
}
return nil;
}
// index += 1
//
// let strippedKeyBytes = [UInt8](byteArray[index...keyData.count - 1])
// let data = Data(bytes: UnsafePointer<UInt8>(strippedKeyBytes), count: keyData.count - index)
index += 1;
NSInteger countOfStrippedBytes = currentData.length - index;
UInt8 *strippedBytes = (UInt8 *)(bytes + index);
NSData *resultData = [[NSData alloc] initWithBytes:strippedBytes length:countOfStrippedBytes];
// return data
return resultData;
}
+ (NSData *)dataByExtractingKeyFromANS1:(NSData *)data error:(NSError *__autoreleasing *)error {
if (data == nil) {
return nil;
}
// look for 03 42 00 04
int8_t bytesToSearchFor[] = {0x03, 0x42, 0x00, 0x04};
int count = sizeof(bytesToSearchFor) / sizeof(bytesToSearchFor[0]);
NSData *dataToSearchFor = [NSData dataWithBytes:bytesToSearchFor length:count];
NSRange fullRange = NSMakeRange(0, data.length);
NSRange foundRange = [data rangeOfData:dataToSearchFor options:0 range:fullRange];
NSData *foundData = nil;
if (foundRange.location != NSNotFound && foundRange.length != 0) {
// try to extract tail of data.
// but we should also preserve 0x04.
// so, one byte less.
NSInteger tailPosition = foundRange.location + foundRange.length - 1;
NSInteger length = data.length - tailPosition;
if (tailPosition >= 0 && length >= 0) {
foundData = [NSData dataWithBytes:((char *)data.bytes) + tailPosition length:length];
}
}
return foundData;
}
@end