IdentityBackupStore.m 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2014-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "IdentityBackupStore.h"
  21. #import "DocumentManager.h"
  22. #ifdef DEBUG
  23. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  24. #else
  25. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  26. #endif
  27. static const NSString *keychainLabel = @"Threema identity backup";
  28. static const NSString *backupFileName = @"idbackup.txt";
  29. @implementation IdentityBackupStore
  30. + (NSString *)loadIdentityBackup {
  31. NSString *backup;
  32. backup = [IdentityBackupStore loadIdentityBackupFromKeychain];
  33. if (backup == nil)
  34. backup = [IdentityBackupStore loadIdentityBackupFromFile];
  35. return backup;
  36. }
  37. + (NSString *)loadIdentityBackupFromKeychain {
  38. NSMutableDictionary *loadDict = [NSMutableDictionary dictionaryWithDictionary:[self queryDict]];
  39. [loadDict setObject:@YES forKey:(__bridge id)kSecReturnData];
  40. CFDataRef resultRef;
  41. if (SecItemCopyMatching((__bridge CFDictionaryRef)loadDict, (CFTypeRef *)&resultRef) == noErr) {
  42. return [[NSString alloc] initWithData:(__bridge NSData*)resultRef encoding:NSASCIIStringEncoding];
  43. }
  44. return nil;
  45. }
  46. + (NSString *)loadIdentityBackupFromFile {
  47. if ([[NSFileManager defaultManager] fileExistsAtPath:[self backupFilePath]]) {
  48. NSString *backup = [NSString stringWithContentsOfFile:[self backupFilePath] encoding:NSUTF8StringEncoding error:nil];
  49. if (backup.length > 0)
  50. return backup;
  51. }
  52. return nil;
  53. }
  54. + (BOOL)saveIdentityBackup:(NSString *)backupData {
  55. BOOL success = YES;
  56. success &= [IdentityBackupStore saveIdentityBackupToKeychain:backupData];
  57. success &= [IdentityBackupStore saveIdentityBackupToFile:backupData];
  58. return success;
  59. }
  60. + (BOOL)saveIdentityBackupToKeychain:(NSString *)backupData {
  61. NSDictionary *queryDict = [self queryDict];
  62. /* check if we already have a keychain item and need to update */
  63. CFDictionaryRef resultRef;
  64. if (SecItemCopyMatching((__bridge CFDictionaryRef)queryDict, (CFTypeRef *)&resultRef) == noErr) {
  65. if (SecItemDelete((__bridge CFDictionaryRef)queryDict) != noErr) {
  66. DDLogError(@"Couldn't delete keychain item");
  67. }
  68. }
  69. /* add new item */
  70. NSDictionary *addDict = @{
  71. (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
  72. (__bridge id)kSecAttrLabel: keychainLabel,
  73. (__bridge id)kSecValueData: [backupData dataUsingEncoding:NSASCIIStringEncoding],
  74. (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
  75. };
  76. OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addDict, NULL);
  77. if (status == noErr) {
  78. return YES;
  79. } else {
  80. DDLogError(@"Couldn't add keychain item, status: %d", (int)status);
  81. return NO;
  82. }
  83. }
  84. + (BOOL)saveIdentityBackupToFile:(NSString *)backupData {
  85. return [backupData writeToFile:[IdentityBackupStore backupFilePath] atomically:NO encoding:NSUTF8StringEncoding error:nil];
  86. }
  87. + (void)deleteIdentityBackup {
  88. SecItemDelete((__bridge CFDictionaryRef)[IdentityBackupStore queryDict]);
  89. [[NSFileManager defaultManager] removeItemAtPath:[self backupFilePath] error:nil];
  90. }
  91. + (NSDictionary*)queryDict {
  92. return @{
  93. (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
  94. (__bridge id)kSecAttrLabel: keychainLabel
  95. };
  96. }
  97. + (NSString*)backupFilePath {
  98. return [[DocumentManager applicationDocumentsDirectory].path stringByAppendingPathComponent:(NSString*)backupFileName];
  99. }
  100. + (void)syncKeychainWithFile {
  101. /* Check for an ID backup in the keychain and in a file within the app data container. If one of them is
  102. missing, restore it (keychain takes priority). We need both the keychain entry and the file, as the
  103. keychain entry survives app deletion/reinstallation, while the file survives device backup/restore
  104. via iCloud. */
  105. NSString *keychainBackup = [IdentityBackupStore loadIdentityBackupFromKeychain];
  106. NSString *fileBackup = [IdentityBackupStore loadIdentityBackupFromFile];
  107. if (keychainBackup.length > 0) {
  108. if (fileBackup == nil || ![keychainBackup isEqualToString:fileBackup]) {
  109. /* Write file again */
  110. DDLogVerbose(@"Recreating file-based ID backup from keychain");
  111. [IdentityBackupStore saveIdentityBackupToFile:keychainBackup];
  112. }
  113. } else if (fileBackup.length > 0) {
  114. if (keychainBackup == nil || ![fileBackup isEqualToString:keychainBackup]) {
  115. /* Write keychain entry again */
  116. DDLogVerbose(@"Recreating keychain ID backup from file");
  117. [IdentityBackupStore saveIdentityBackupToKeychain:fileBackup];
  118. }
  119. }
  120. }
  121. @end