2020-02-11 17:42:54 +01:00

486 lines
17 KiB
Objective-C

#import "BuildConfig.h"
static NSString *telegramApplicationSecretKey = @"telegramApplicationSecretKey_v3";
API_AVAILABLE(ios(10))
@interface LocalPrivateKey : NSObject {
SecKeyRef _privateKey;
SecKeyRef _publicKey;
}
- (NSData * _Nullable)encrypt:(NSData * _Nonnull)data;
- (NSData * _Nullable)decrypt:(NSData * _Nonnull)data cancelled:(bool *)cancelled;
@end
@implementation LocalPrivateKey
- (instancetype _Nonnull)initWithPrivateKey:(SecKeyRef)privateKey publicKey:(SecKeyRef)publicKey {
self = [super init];
if (self != nil) {
_privateKey = (SecKeyRef)CFRetain(privateKey);
_publicKey = (SecKeyRef)CFRetain(publicKey);
}
return self;
}
- (void)dealloc {
CFRelease(_privateKey);
CFRelease(_publicKey);
}
- (NSData * _Nullable)getPublicKey {
NSData *result = CFBridgingRelease(SecKeyCopyExternalRepresentation(_publicKey, nil));
return result;
}
- (NSData * _Nullable)encrypt:(NSData * _Nonnull)data {
if (data.length % 16 != 0) {
return nil;
}
CFErrorRef error = NULL;
NSData *cipherText = (NSData *)CFBridgingRelease(SecKeyCreateEncryptedData(_publicKey, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM, (__bridge CFDataRef)data, &error));
if (!cipherText) {
__unused NSError *err = CFBridgingRelease(error);
return nil;
}
return cipherText;
}
- (NSData * _Nullable)decrypt:(NSData * _Nonnull)data cancelled:(bool *)cancelled {
CFErrorRef error = NULL;
NSData *plainText = (NSData *)CFBridgingRelease(SecKeyCreateDecryptedData(_privateKey, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM, (__bridge CFDataRef)data, &error));
if (!plainText) {
__unused NSError *err = CFBridgingRelease(error);
if (err.code == -2) {
if (cancelled) {
*cancelled = true;
}
}
return nil;
}
return plainText;
}
@end
@interface BuildConfig () {
NSData * _Nullable _bundleData;
int32_t _apiId;
NSString * _Nonnull _apiHash;
NSString * _Nullable _appCenterId;
NSMutableDictionary * _Nonnull _dataDict;
}
@end
@implementation DeviceSpecificEncryptionParameters
- (instancetype)initWithKey:(NSData * _Nonnull)key salt:(NSData * _Nonnull)salt {
self = [super init];
if (self != nil) {
_key = key;
_salt = salt;
}
return self;
}
@end
@implementation BuildConfig
+ (NSString *)bundleId {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
@"bundleSeedID", kSecAttrAccount,
@"", kSecAttrService,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
}
if (status != errSecSuccess) {
return nil;
}
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
+ (instancetype _Nonnull)sharedBuildConfig {
static BuildConfig *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[BuildConfig alloc] init];
});
return instance;
}
- (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId {
self = [super init];
if (self != nil) {
_apiId = APP_CONFIG_API_ID;
_apiHash = @(APP_CONFIG_API_HASH);
_appCenterId = @(APP_CONFIG_APP_CENTER_ID);
_dataDict = [[NSMutableDictionary alloc] init];
if (baseAppBundleId != nil) {
_dataDict[@"bundleId"] = baseAppBundleId;
}
}
return self;
}
- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict {
NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] initWithDictionary:_dataDict];
if (appToken != nil) {
dataDict[@"device_token"] = [appToken base64EncodedStringWithOptions:0];
dataDict[@"device_token_type"] = @"voip";
}
if (signatureDict != nil) {
for (id<NSCopying> key in signatureDict.allKeys) {
dataDict[key] = signatureDict[key];
}
}
NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict options:0 error:nil];
return data;
}
- (int32_t)apiId {
return _apiId;
}
- (NSString * _Nonnull)apiHash {
return _apiHash;
}
- (NSString * _Nullable)appCenterId {
return _appCenterId;
}
- (bool)isInternalBuild {
return APP_CONFIG_IS_INTERNAL_BUILD;
}
- (bool)isAppStoreBuild {
return APP_CONFIG_IS_APPSTORE_BUILD;
}
- (int64_t)appStoreId {
return APP_CONFIG_APPSTORE_ID;
}
- (NSString *)appSpecificUrlScheme {
return @(APP_SPECIFIC_URL_SCHEME);
}
+ (NSString * _Nullable)bundleSeedId {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
@"bundleSeedID", kSecAttrAccount,
@"", kSecAttrService,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
}
if (status != errSecSuccess) {
return nil;
}
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
+ (NSString * _Nonnull)applicationSecretTag:(bool)isCheckKey {
if (isCheckKey) {
return [[telegramApplicationSecretKey stringByAppendingString:@"_check"] dataUsingEncoding:NSUTF8StringEncoding];
} else {
return [telegramApplicationSecretKey dataUsingEncoding:NSUTF8StringEncoding];
}
}
+ (LocalPrivateKey * _Nullable)getApplicationSecretKey:(NSString * _Nonnull)baseAppBundleId isCheckKey:(bool)isCheckKey API_AVAILABLE(ios(10)) {
NSString *bundleSeedId = [self bundleSeedId];
if (bundleSeedId == nil) {
return nil;
}
NSData *applicationTag = [self applicationSecretTag:isCheckKey];
NSString *accessGroup = [bundleSeedId stringByAppendingFormat:@".%@", baseAppBundleId];
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: applicationTag,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
(id)kSecAttrAccessGroup: (id)accessGroup,
(id)kSecReturnRef: @YES
};
SecKeyRef privateKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);
if (status != errSecSuccess) {
return nil;
}
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
if (!publicKey) {
if (privateKey) {
CFRelease(privateKey);
}
return nil;
}
LocalPrivateKey *result = [[LocalPrivateKey alloc] initWithPrivateKey:privateKey publicKey:publicKey];
if (publicKey) {
CFRelease(publicKey);
}
if (privateKey) {
CFRelease(privateKey);
}
return result;
}
+ (bool)removeApplicationSecretKey:(NSString * _Nonnull)baseAppBundleId isCheckKey:(bool)isCheckKey API_AVAILABLE(ios(10)) {
NSString *bundleSeedId = [self bundleSeedId];
if (bundleSeedId == nil) {
return nil;
}
NSData *applicationTag = [self applicationSecretTag:isCheckKey];
NSString *accessGroup = [bundleSeedId stringByAppendingFormat:@".%@", baseAppBundleId];
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: applicationTag,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
(id)kSecAttrAccessGroup: (id)accessGroup
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess) {
return false;
}
return true;
}
+ (LocalPrivateKey * _Nullable)addApplicationSecretKey:(NSString * _Nonnull)baseAppBundleId isCheckKey:(bool)isCheckKey API_AVAILABLE(ios(10)) {
NSString *bundleSeedId = [self bundleSeedId];
if (bundleSeedId == nil) {
return nil;
}
NSData *applicationTag = [self applicationSecretTag:isCheckKey];
NSString *accessGroup = [bundleSeedId stringByAppendingFormat:@".%@", baseAppBundleId];
SecAccessControlRef access;
if (isCheckKey) {
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage, NULL);
} else {
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlUserPresence | kSecAccessControlPrivateKeyUsage, NULL);
}
NSDictionary *attributes = @{
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
(id)kSecAttrKeySizeInBits: @256,
(id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
(id)kSecPrivateKeyAttrs: @{
(id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: applicationTag,
(id)kSecAttrAccessControl: (__bridge id)access,
(id)kSecAttrAccessGroup: (id)accessGroup,
},
};
CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);
if (!privateKey) {
if (access) {
CFRelease(access);
}
__unused NSError *err = CFBridgingRelease(error);
return nil;
}
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
if (!publicKey) {
if (privateKey) {
CFRelease(privateKey);
}
if (access) {
CFRelease(access);
}
__unused NSError *err = CFBridgingRelease(error);
return nil;
}
LocalPrivateKey *result = [[LocalPrivateKey alloc] initWithPrivateKey:privateKey publicKey:publicKey];
if (publicKey) {
CFRelease(publicKey);
}
if (privateKey) {
CFRelease(privateKey);
}
if (access) {
CFRelease(access);
}
return result;
}
+ (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId {
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSString *filePath = [rootPath stringByAppendingPathComponent:@".tempkey"];
NSString *encryptedPath = [rootPath stringByAppendingPathComponent:@".tempkeyEncrypted"];
NSData *currentData = [NSData dataWithContentsOfFile:filePath];
NSData *resultData = nil;
if (currentData != nil && currentData.length == 32 + 16) {
resultData = currentData;
}
if (resultData == nil) {
NSMutableData *randomData = [[NSMutableData alloc] initWithLength:32 + 16];
int result = SecRandomCopyBytes(kSecRandomDefault, randomData.length, [randomData mutableBytes]);
if (currentData != nil && currentData.length == 32) { // upgrade key with salt
[currentData getBytes:randomData.mutableBytes length:32];
}
assert(result == 0);
resultData = randomData;
[resultData writeToFile:filePath atomically:false];
}
/*if (@available(iOS 11, *)) {
NSData *currentEncryptedData = [NSData dataWithContentsOfFile:encryptedPath];
LocalPrivateKey *localPrivateKey = [self getLocalPrivateKey:baseAppBundleId];
if (localPrivateKey == nil) {
localPrivateKey = [self addLocalPrivateKey:baseAppBundleId];
}
if (localPrivateKey != nil) {
if (currentEncryptedData != nil) {
NSData *decryptedData = [localPrivateKey decrypt:currentEncryptedData];
if (![resultData isEqualToData:decryptedData]) {
NSData *encryptedData = [localPrivateKey encrypt:resultData];
[encryptedData writeToFile:encryptedPath atomically:false];
//assert(false);
}
} else {
NSData *encryptedData = [localPrivateKey encrypt:resultData];
[encryptedData writeToFile:encryptedPath atomically:false];
}
}
}*/
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
NSLog(@"deviceSpecificEncryptionParameters took %f ms", (endTime - startTime) * 1000.0);
NSData *key = [resultData subdataWithRange:NSMakeRange(0, 32)];
NSData *salt = [resultData subdataWithRange:NSMakeRange(32, 16)];
return [[DeviceSpecificEncryptionParameters alloc] initWithKey:key salt:salt];
}
+ (dispatch_queue_t)encryptionQueue {
static dispatch_queue_t instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = dispatch_queue_create("encryptionQueue", 0);
});
return instance;
}
+ (void)getHardwareEncryptionAvailableWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion {
dispatch_async([self encryptionQueue], ^{
LocalPrivateKey *checkKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:true];
if (checkKey != nil) {
NSData *sampleData = [checkKey encrypt:[NSData data]];
if (sampleData == nil) {
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:false];
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:true];
} else {
NSData *decryptedData = [checkKey decrypt:sampleData cancelled: nil];
if (decryptedData == nil) {
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:false];
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:true];
}
}
} else {
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:false];
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:true];
}
LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false];
if (privateKey == nil) {
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:false];
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:true];
privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:false];
privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:true];
}
completion([privateKey getPublicKey]);
});
}
+ (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, NSData * _Nullable))completion {
dispatch_async([self encryptionQueue], ^{
LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false];
if (privateKey == nil) {
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:false];
[self removeApplicationSecretKey:baseAppBundleId isCheckKey:true];
privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:false];
privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:true];
}
if (privateKey == nil) {
completion(nil, nil);
return;
}
NSData *result = [privateKey encrypt:secret];
completion(result, [privateKey getPublicKey]);
});
}
+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, bool))completion {
dispatch_async([self encryptionQueue], ^{
LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false];
if (privateKey == nil) {
completion(nil, false);
return;
}
if (privateKey == nil) {
completion(nil, false);
return;
}
NSData *currentPublicKey = [privateKey getPublicKey];
if (currentPublicKey == nil) {
completion(nil, false);
return;
}
if (![publicKey isEqualToData:currentPublicKey]) {
completion(nil, false);
return;
}
bool cancelled = false;
NSData *result = [privateKey decrypt:secret cancelled:&cancelled];
completion(result, cancelled);
});
}
@end