mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
488 lines
17 KiB
Objective-C
488 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";
|
|
}
|
|
float tzOffset = ([[NSTimeZone systemTimeZone] secondsFromGMT] / 3600.0);
|
|
dataDict[@"tz_offset"] = @((int)tzOffset);
|
|
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
|