-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTweak.x
More file actions
150 lines (118 loc) · 5.03 KB
/
Tweak.x
File metadata and controls
150 lines (118 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#import <UIKit/UIKit.h>
#import <Security/Security.h>
@interface AccountTableViewController : UITableViewController
@end
// need to store this to present the alert
static AccountTableViewController *tableVC = nil;
static void presentAlert(NSString *message, NSString *outputString) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"2FA Export" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Copy otpauth URIs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[[UIPasteboard generalPasteboard] setString:outputString];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
[tableVC presentViewController:alert animated:YES completion:nil];
}
static BOOL processKeychainItem(NSDictionary *item, NSDictionary **outJSON, NSString **outError) {
NSString *displayName = item[(__bridge id)kSecAttrAccount];
NSData *passwordData = item[(__bridge id)kSecValueData];
if (!passwordData) {
*outError = [NSString stringWithFormat:@"No password data found for item '%@'", displayName];
return NO;
}
NSError *jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:passwordData options:0 error:&jsonError];
if (!json) {
*outError = [NSString stringWithFormat:@"Invalid password data for item '%@' (error: %@)", displayName, jsonError];
return NO;
}
// Microsoft accounts (maybe others too?) are not supported
if (!json[@"AccountOathSecretKey"] || ![json[@"AccountType"]isEqual:@(2)]) {
*outError = [NSString stringWithFormat:@"Account type not supported for item '%@'", displayName];
return NO;
}
*outJSON = json;
return YES;
}
static NSString *generateOTPAuthURI(NSDictionary *json) {
NSString *accountName = json[@"AccountName"];
NSString *accountUsername = json[@"AccountUsername"];
NSString *secretKey = [json[@"AccountOathSecretKey"] stringByReplacingOccurrencesOfString:@" " withString:@""];
return [NSString stringWithFormat:@"otpauth://totp/%@:%@?secret=%@&issuer=%@", accountName, accountUsername, secretKey, accountName];
// Ente does not like URL encoding and seems to work fine without it
// keeping this commented in case it's needed in the future
// return [uri stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
}
static void handleResults(NSArray *successfulItems, NSArray *failedErrors) {
NSMutableString *outputString = [NSMutableString string];
NSMutableString *alertString = [NSMutableString string];
if (failedErrors.count > 0) {
[alertString appendString:@"Could not process the following items:\n"];
for (NSDictionary *json in failedErrors) {
[alertString appendFormat:@"- %@\n", json];
}
[alertString appendString:@"\n"];
}
[alertString appendString:@"Successfully processed the following items:\n"];
for (NSDictionary *json in successfulItems) {
[outputString appendFormat:@"%@\n", generateOTPAuthURI(json)];
[alertString appendFormat:@"- %@ (%@)\n", json[@"AccountName"], json[@"AccountUsername"]];
}
presentAlert(alertString, outputString);
}
static void processKeychain() {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"com.microsoft.authenticator.backup",
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
(__bridge id)kSecAttrSynchronizable: @YES,
(__bridge id)kSecReturnAttributes: @YES,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll
};
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (status == errSecSuccess && result != NULL) {
NSArray *items = (__bridge_transfer NSArray *)result;
NSMutableArray *successfulItems = [NSMutableArray array];
NSMutableArray *failedErrors = [NSMutableArray array];
for (NSDictionary *item in items) {
NSDictionary *json = nil;
NSString *errorMessage = nil;
if (processKeychainItem(item, &json, &errorMessage)) {
[successfulItems addObject:json];
} else {
[failedErrors addObject:errorMessage];
}
}
handleResults(successfulItems, failedErrors);
} else if (status == errSecItemNotFound) {
NSLog(@"No keychain entries found");
} else {
NSLog(@"Keychain query failed with status: %d", (int)status);
}
}
%hook AccountTableViewController
-(void)viewDidLoad {
%orig;
tableVC = self;
}
%end
%hook UINavigationItem
-(void)setRightBarButtonItems:(NSArray *)rightBarButtonItems {
// ignore if on wrong page, or if we haven't grabbed a vc instance
if (rightBarButtonItems.count != 2 || !tableVC) {
%orig;
return;
}
// create export button
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"square.and.arrow.up"] style:UIBarButtonItemStylePlain target:self action:@selector(export_2fa)];
// add to nav bar
NSMutableArray *items = [rightBarButtonItems mutableCopy];
[items addObject:button];
%orig(items);
}
%new
-(void)export_2fa {
processKeychain();
}
%end