1
1
#import < objc/runtime.h>
2
2
#import < Foundation/Foundation.h>
3
3
#import < React/RCTHTTPRequestHandler.h>
4
+ #import < Security/Security.h>
4
5
6
+ /* *
7
+ * There will be only one instance of this class on runtime accordinly the React Native code.
8
+ * This provides a good opportunity to cache data on the class itself.
9
+ */
5
10
@implementation RCTHTTPRequestHandler (AuthenticationChallengeExtension)
6
11
12
+ // Cache allocated certificates on memory to hold them through the class lifecycle.
13
+ static NSMutableDictionary <NSString *, id > *_certificateCache = nil ;
14
+ // Cache a set of allowed domains
7
15
static NSSet *_cachedAllowedDomains = nil ;
8
16
9
- /* The load method is called by the Objective-C runtime when the class is loaded into memory,
17
+ /* *
18
+ * The load method is called by the Objective-C runtime when the class is loaded into memory,
10
19
* it happens early in the application's lifecycle.
11
20
*/
12
21
+ (void )load {
13
22
// dispatch_once avoids the load method to run twice
14
23
static dispatch_once_t onceToken;
15
24
dispatch_once (&onceToken, ^{
16
- NSLog (@" [RCTHTTPRequestHandler extension] It will swizzle authentication challenge on this data delegate." );
17
25
Class class = [RCTHTTPRequestHandler class ];
18
26
19
- SEL originalSelector = @selector (URLSession:task:didReceiveChallenge:completionHandler: );
20
- SEL swizzledSelector = @selector (authenticationChallenge_URLSession:task:didReceiveChallenge:completionHandler: );
27
+ NSLog (@" [RCTHTTPRequestHandler (AuthenticationChallengeExtension)] Initializing _certificateCache." );
28
+ _certificateCache = [NSMutableDictionary new ];
29
+
30
+ NSLog (@" [RCTHTTPRequestHandler (AuthenticationChallengeExtension)] Swizzling authentication challenge handler on session scope." );
31
+ SEL originalSelector = @selector (URLSession:didReceiveChallenge:completionHandler: );
32
+ SEL swizzledSelector = @selector (authenticationChallenge_URLSession:didReceiveChallenge:completionHandler: );
21
33
22
34
Method originalMethod = class_getInstanceMethod (class, originalSelector);
23
35
Method swizzledMethod = class_getInstanceMethod (class, swizzledSelector);
@@ -29,10 +41,24 @@ + (void)load {
29
41
} else {
30
42
method_exchangeImplementations (originalMethod, swizzledMethod);
31
43
}
44
+
45
+ NSLog (@" [RCTHTTPRequestHandler (AuthenticationChallengeExtension)] Swizzling the dealloc method." );
46
+ SEL deallocOriginalSelector = NSSelectorFromString (@" dealloc" );
47
+ SEL deallocSwizzledSelector = @selector (swizzled_dealloc );
48
+
49
+ Method deallocOriginalMethod = class_getInstanceMethod (class, deallocOriginalSelector);
50
+ Method deallocSwizzledMethod = class_getInstanceMethod (class, deallocSwizzledSelector);
51
+
52
+ if (!class_addMethod (class, deallocOriginalSelector, method_getImplementation (deallocSwizzledMethod), method_getTypeEncoding (deallocSwizzledMethod))) {
53
+ method_exchangeImplementations (deallocOriginalMethod, deallocSwizzledMethod);
54
+ } else {
55
+ class_replaceMethod (class, deallocSwizzledSelector, method_getImplementation (deallocOriginalMethod), method_getTypeEncoding (deallocOriginalMethod));
56
+ }
32
57
});
33
58
}
34
59
35
- /* This method extends the RCTHTTPRequestHandler by intercepting the authentication challenge,
60
+ /* *
61
+ * This method extends the RCTHTTPRequestHandler by intercepting the authentication challenge,
36
62
* and gives to it a custom behavior regarding allowing or denying connections.
37
63
* The current implementation of RCTHTTPRequestHandler doesn't provide an implementation for
38
64
* the authentication challenge event, therefore we don't risk to interfere with React Native behavior.
@@ -45,38 +71,99 @@ + (void)load {
45
71
* In React Native, when a session is created, this delegate is injected in.
46
72
*/
47
73
- (void )authenticationChallenge_URLSession : (NSURLSession *)session
48
- task : (NSURLSessionTask *)task
49
74
didReceiveChallenge : (NSURLAuthenticationChallenge *)challenge
50
75
completionHandler : (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
51
- // Set of allowed domains
52
- NSSet *allowedDomains = [self getCachedAllowedDomains ];
53
- // Check if the challenge is of type ServerTrust
76
+ static int _counter = 0 ;
77
+ NSLog (@" Session handler call %i , host %@ ." , _counter, challenge.protectionSpace .host );
78
+ _counter++;
79
+
54
80
if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodServerTrust ]) {
55
- // Check if the host is an allowed domain
56
- if ([self checkValidityOfHost: challenge.protectionSpace.host allowedInDomains: allowedDomains]) {
57
- // Check if proceed with the original authentication handling
58
- if ([self checkValidityOfTrust: challenge.protectionSpace.serverTrust]) {
59
- // The challenge is of type ServerTrust, the host is allowed and the certificate is trust worthy
60
- // Create the credential and completes the delegate
61
- NSURLCredential *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
62
- completionHandler (NSURLSessionAuthChallengeUseCredential , credential);
81
+ NSArray *anchorCertificates = [self getAnchorCertificates ];
82
+ // Set resource certificates to the trust entity to be evaluated
83
+ OSStatus status = SecTrustSetAnchorCertificates (challenge.protectionSpace .serverTrust , (__bridge CFArrayRef )anchorCertificates);
84
+ if (status == errSecSuccess) {
85
+ /* The following method signs to the evalution if the system bundle certificates must be taken in consideration
86
+ * when evaluating the trust entity. The second param determines this behavior, set YES to constrain the evaluation
87
+ * to the resource certificates only, and NO to allow system bundle certificates.
88
+ */
89
+ SecTrustSetAnchorCertificatesOnly (challenge.protectionSpace .serverTrust , YES );
90
+ // A set of allowed domains
91
+ NSSet *allowedDomains = [self getCachedAllowedDomains ];
92
+ // Check if the host is an allowed domain
93
+ if ([self checkValidityOfHost: challenge.protectionSpace.host allowedInDomains: allowedDomains]) {
94
+ // Check if proceed with the original authentication handling
95
+ if ([self checkValidityOfTrust: challenge.protectionSpace.serverTrust]) {
96
+ // The challenge is of type ServerTrust, the host is allowed and the certificate is trust worthy
97
+ // Create the credential and completes the delegate
98
+ NSURLCredential *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
99
+ completionHandler (NSURLSessionAuthChallengeUseCredential , credential);
100
+ } else {
101
+ // Certificate is invalid, cancelling authentication
102
+ NSLog (@" Invalid certificate for Domain %@ . Cancelling authentication." , challenge.protectionSpace .host );
103
+ // Cancel the authentication challenge
104
+ completionHandler (NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil );
105
+ }
63
106
} else {
64
- // Certificate is invalid , cancelling authentication
65
- NSLog (@" Invalid certificate for Domain %@ . Cancelling authentication." , challenge.protectionSpace .host );
107
+ // Host is not allowed , cancelling authentication
108
+ NSLog (@" Host domain %@ is not allowed . Cancelling authentication." , challenge.protectionSpace .host );
66
109
// Cancel the authentication challenge
67
110
completionHandler (NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil );
68
111
}
69
112
} else {
70
- // Host is not allowed, cancelling authentication
71
- NSLog (@" Host domain %@ is not allowed. Cancelling authentication." , challenge.protectionSpace .host );
113
+ NSLog (@" Certificate not set." );
72
114
// Cancel the authentication challenge
73
115
completionHandler (NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil );
74
116
}
75
117
} else {
76
118
// Trust is not of type ServerTrust, proceeding with default challenge, if any
77
119
NSLog (@" Not ServerTrust challenge. Calling default authentication challenge." );
78
120
// For other authentication methods, call the original implementation
79
- [self authenticationChallenge_URLSession: session task: task didReceiveChallenge: challenge completionHandler: completionHandler];
121
+ [self authenticationChallenge_URLSession: session didReceiveChallenge: challenge completionHandler: completionHandler];
122
+ }
123
+ }
124
+
125
+ /* *
126
+ * Fetch anchor certificates either from the resources or the cache.
127
+ */
128
+ - (NSArray *)getAnchorCertificates {
129
+ // Load root certificates from resources in the first run and from the cache afterwards
130
+ SecCertificateRef hathor_root_ca_1 = [self getCert: @" hathor_network_root_ca_1" ];
131
+ SecCertificateRef hathor_root_ca_2 = [self getCert: @" hathor_network_root_ca_2" ];
132
+
133
+ // Create an NSArray with the certificates
134
+ NSArray *certArray = @[(__bridge id )hathor_root_ca_1, (__bridge id )hathor_root_ca_2];
135
+
136
+ return certArray;
137
+ }
138
+
139
+ /* *
140
+ * The certificate must be of type .der and should be in DER encoding.
141
+ */
142
+ - (SecCertificateRef)getCert : (NSString *)certFilename {
143
+ @synchronized (_certificateCache) {
144
+ id cachedCert = [_certificateCache objectForKey: certFilename];;
145
+ if (cachedCert) {
146
+ return (__bridge SecCertificateRef)cachedCert;
147
+ }
148
+
149
+ NSString *certPath = [[NSBundle mainBundle ] pathForResource: certFilename ofType: @" der" ];
150
+ NSData *certData = [NSData dataWithContentsOfFile: certPath];
151
+
152
+ if (!certData) {
153
+ @throw [NSException exceptionWithName: NSInternalInconsistencyException
154
+ reason: [NSString stringWithFormat: @" %@ not found" , certFilename]
155
+ userInfo: nil ];
156
+ }
157
+
158
+ SecCertificateRef cert = SecCertificateCreateWithData (NULL , (__bridge CFDataRef )certData);
159
+ if (!cert) {
160
+ @throw [NSException exceptionWithName: NSInternalInconsistencyException
161
+ reason: [NSString stringWithFormat: @" Error on parsing the %@ certificate" , certFilename]
162
+ userInfo: nil ];
163
+ }
164
+
165
+ [_certificateCache setObject: (__bridge id )cert forKey: certFilename];
166
+ return cert;
80
167
}
81
168
}
82
169
@@ -206,4 +293,27 @@ - (BOOL)checkValidityOfHost:(NSString *)host allowedInDomains:(NSSet *)allowedDo
206
293
return NO ;
207
294
}
208
295
296
+ /* *
297
+ * Release each allocated certificate in memory and clean the cache dictionary in a thread safe manner.
298
+ */
299
+ - (void )clearCertificateCache {
300
+ NSLog (@" [RCTHTTPRequestHandler (AuthenticationChallengeExtension)] Clearing the certificate cache." );
301
+ @synchronized (_certificateCache) {
302
+ for (id cert in _certificateCache.allValues ) {
303
+ CFRelease ((__bridge SecCertificateRef)cert);
304
+ }
305
+ [_certificateCache removeAllObjects ];
306
+ }
307
+ }
308
+
309
+ /* *
310
+ * As this class has only one instance and it will probably live until the user quits the app,
311
+ * it will probably never be called. However, we shouldn't worry much about it because
312
+ * the app memory will be released anyway after quit.
313
+ */
314
+ - (void )swizzled_dealloc {
315
+ [self clearCertificateCache ];
316
+ [self swizzled_dealloc ]; // This will call the original dealloc method
317
+ }
318
+
209
319
@end
0 commit comments