Просмотр исходного кода

Integrate OCSPCache

Motivation:
OCSP requests are made in plaintext by
the system on iOS. These requests are
made outside of the tunnel.

Modifications:
Make OCSP requests manually through
the tunnel with OCSPCache.

Result:
OCSP requests cannot be intercepted
between the client and server.
mirokuratczyk 6 лет назад
Родитель
Сommit
f230c8e212
19 измененных файлов с 231 добавлено и 481 удалено
  1. 8 4
      MobileLibrary/iOS/SampleApps/Common/AuthURLSessionTaskDelegate.h
  2. 87 73
      MobileLibrary/iOS/SampleApps/Common/AuthURLSessionTaskDelegate.m
  3. 0 30
      MobileLibrary/iOS/SampleApps/Common/OCSP.h
  4. 0 232
      MobileLibrary/iOS/SampleApps/Common/OCSP.m
  5. 0 24
      MobileLibrary/iOS/SampleApps/Common/URLEncode.h
  6. 0 39
      MobileLibrary/iOS/SampleApps/Common/URLEncode.m
  7. 2 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/Podfile
  8. 14 1
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/Podfile.lock
  9. 0 12
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.pbxproj
  10. 85 44
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/AppDelegate.swift
  11. 4 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Info.plist
  12. 1 1
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/TunneledWebRequest-Bridging-Header.h
  13. 1 3
      MobileLibrary/iOS/SampleApps/TunneledWebView/External/JiveAuthenticatingHTTPProtocol/JAHPAuthenticatingHTTPProtocol.m
  14. 2 0
      MobileLibrary/iOS/SampleApps/TunneledWebView/Podfile
  15. 14 1
      MobileLibrary/iOS/SampleApps/TunneledWebView/Podfile.lock
  16. 0 12
      MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView.xcodeproj/project.pbxproj
  17. 1 2
      MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView/AppDelegate.swift
  18. 3 1
      MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView/Info.plist
  19. 9 2
      MobileLibrary/iOS/USAGE.md

+ 8 - 4
MobileLibrary/iOS/SampleApps/Common/AuthURLSessionTaskDelegate.h

@@ -11,10 +11,11 @@
 // NOTE: this file is shared by TunneledWebRequest and TunneledWebView
 
 #import <Foundation/Foundation.h>
+#import "OCSPCache.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-/*
+/*!
  * AuthURLSessionTaskDelegate implements URLSession:task:didReceiveChallenge:completionHandler:
  * of the NSURLSessionTaskDelegate protocol.
  *
@@ -31,18 +32,20 @@ NS_ASSUME_NONNULL_BEGIN
  * (1.3.6.1.5.5.7.48.1) Authority Information Access Method, which contains the locations (URLs) of
  * the OCSP servers; then OCSP requests are then made to these servers through the local HTTP proxy.
  *
+ * Note: AuthURLSessionTaskDelegate only checks revocation status with OCSP.
+ *
  * Note: The OCSP Authority Information Access Method is found in the Certificate Authority
  *       Information Access (1.3.6.1.5.5.7.1.1) X.509v3 extension --
  *       https://tools.ietf.org/html/rfc2459#section-4.2.2.1.
  */
 @interface AuthURLSessionTaskDelegate : NSObject <NSURLSessionDelegate>
 
-/*
+/*!
  * Logger for errors.
  */
 @property (nonatomic, strong) void (^logger)(NSString*);
 
-/*
+/*!
  * Local HTTP proxy port.
  *
  * OCSP request URL is constructed as:
@@ -50,7 +53,8 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (atomic, assign) NSInteger localHTTPProxyPort;
 
-- (id)initWithLogger:(void (^)(NSString*))logger andLocalHTTPProxyPort:(NSInteger)port;
+-  (id)initWithLogger:(void (^)(NSString*))logger
+andLocalHTTPProxyPort:(NSInteger)port;
 
 - (void)URLSession:(NSURLSession *)session
               task:(NSURLSessionTask *)task

+ 87 - 73
MobileLibrary/iOS/SampleApps/Common/AuthURLSessionTaskDelegate.m

@@ -12,17 +12,23 @@
 
 #import "AuthURLSessionTaskDelegate.h"
 
-#import "OCSP.h"
-#import "URLEncode.h"
+#import "OCSPCache.h"
+#import "OCSPURLEncode.h"
 
-@implementation AuthURLSessionTaskDelegate
+@implementation AuthURLSessionTaskDelegate {
+    OCSPCache *ocspCache;
+}
 
-- (id)initWithLogger:(void (^)(NSString*))logger andLocalHTTPProxyPort:(NSInteger)port{
+-  (id)initWithLogger:(void (^)(NSString*))logger
+andLocalHTTPProxyPort:(NSInteger)port {
     self = [super init];
 
     if (self) {
         self.logger = logger;
         self.localHTTPProxyPort = port;
+        self->ocspCache = [[OCSPCache alloc] initWithLogger:^(NSString * _Nonnull logLine) {
+            [self logWithFormat:@"[OCSPCache] %@", logLine];
+        }];
     }
 
     return self;
@@ -56,86 +62,76 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
          challenge.protectionSpace.host,
          [task.currentRequest mainDocumentURL],
          [task.currentRequest URL]];
-        
+
         SecTrustRef trust = challenge.protectionSpace.serverTrust;
         if (trust == nil) {
             assert(NO);
         }
-        
+
         SecPolicyRef policy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod |
                                                         kSecRevocationRequirePositiveResponse |
                                                         kSecRevocationNetworkAccessDisabled);
         SecTrustSetPolicies(trust, policy);
         CFRelease(policy);
-        
-        NSError *e;
-        
-        NSArray <NSURL*>* ocspURLs = [OCSP ocspURLs:trust error:&e];
-        if (e != nil) {
-            [self logWithFormat:@"Error constructing OCSP URLs: %@", e.localizedDescription];
-            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
-            return;
-        }
-        
-        if ([ocspURLs count] == 0) {
-            [self logWithFormat:
-             @"Error no OCSP URLs in the Certificate Authority Information Access "
-             "(1.3.6.1.5.5.7.1.1) extension."];
-            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+
+        // Check if there is a pinned or cached OCSP response
+
+        SecTrustResultType trustResultType;
+        SecTrustEvaluate(trust, &trustResultType);
+
+        if (   trustResultType == kSecTrustResultProceed
+            || trustResultType == kSecTrustResultUnspecified) {
+            [self logWithFormat:@"Pinned or cached OCSP response found by the system"];
+            NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
+            assert(credential != nil);
+            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
             return;
         }
-        
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
-            for (NSURL *ocspURL in ocspURLs) {
-                
-                // The target URL must be encoded, so as to be valid within a query parameter.
-                NSString *encodedTargetUrl = [URLEncode encode:ocspURL.absoluteString];
-                
-                NSNumber *httpProxyPort = [NSNumber numberWithInt:
-                                           (int)self.localHTTPProxyPort];
-                
-                NSString *proxiedURLString = [NSString stringWithFormat:@"http://127.0.0.1:%@"
-                                              "/tunneled/%@",
-                                              httpProxyPort,
-                                              encodedTargetUrl];
-                NSURL *proxiedURL = [NSURL URLWithString:proxiedURLString];
-                if (proxiedURL == nil) {
-                    [self logWithFormat:@"Constructed invalid URL for OCSP request: %@",
-                                        proxiedURLString];
-                    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
-                    return;
-                }
-                
-                NSURLRequest *ocspReq = [NSURLRequest requestWithURL:proxiedURL];
-
-                NSURLResponse *resp = nil;
-                NSError *e = nil;
-                NSData *data = [NSURLConnection sendSynchronousRequest:ocspReq
-                                                     returningResponse:&resp
-                                                                 error:&e];
-                if (e != nil) {
-                    [self logWithFormat:@"Error with OCSP request: %@", e.localizedDescription];
-                    continue;
-                }
-                
-                CFDataRef d = (__bridge CFDataRef)data;
-                SecTrustSetOCSPResponse(trust, d);
-                
-                SecTrustResultType trustResultType;
-                SecTrustEvaluate(trust, &trustResultType);
-
-                if (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultUnspecified) {
-                    NSURLCredential *credential = [NSURLCredential credentialForTrust:
-                                                   challenge.protectionSpace.serverTrust];
-                    assert(credential != nil);
-                    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
-                    return;
-                }
-
-                completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
-                return;
-            }
-        });
+
+        // No pinned OCSP response, try fetching one
+
+        [self logWithFormat:@"Fetching OCSP response through OCSPCache"];
+
+        NSURL* (^modifyOCSPURL)(NSURL *url) = ^NSURL*(NSURL *url) {
+            return [self modifyOCSPURL:url];
+        };
+
+        [ocspCache lookup:trust
+               andTimeout:0
+            modifyOCSPURL:modifyOCSPURL
+               completion:
+         ^(OCSPCacheLookupResult * _Nonnull result) {
+
+             assert(result.response != nil);
+             assert(result.err == nil);
+
+             if (result.cached) {
+                 [self logWithFormat:@"Got cached OCSP response from OCSPCache"];
+             } else {
+                 [self logWithFormat:@"Fetched OCSP response from remote"];
+             }
+
+             CFDataRef d = (__bridge CFDataRef)result.response.data;
+
+             SecTrustSetOCSPResponse(trust, d);
+
+             SecTrustResultType trustResultType;
+             SecTrustEvaluate(trust, &trustResultType);
+
+             if (   trustResultType == kSecTrustResultProceed
+                 || trustResultType == kSecTrustResultUnspecified) {
+                 NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
+                 assert(credential != nil);
+                 completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+                 return;
+             }
+
+             // Reject the protection space.
+             // Do not use NSURLSessionAuthChallengePerformDefaultHandling because it can trigger
+             // plaintext OCSP requests.
+             completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
+             return;
+        }];
 
         return;
     }
@@ -143,4 +139,22 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
 }
 
+// Modify the OCSP URLs so they use the local HTTP proxy
+- (nonnull NSURL *)modifyOCSPURL:(nonnull NSURL *)url {
+
+    // The target URL must be encoded, so as to be valid within a query parameter.
+    NSString *encodedTargetUrl = [URLEncode encode:url.absoluteString];
+
+    NSNumber *httpProxyPort = [NSNumber numberWithInt:(int)self.localHTTPProxyPort];
+
+    NSString *proxiedURLString = [NSString stringWithFormat:@"http://127.0.0.1:%@/tunneled/%@",
+                                                            httpProxyPort,
+                                                            encodedTargetUrl];
+    NSURL *proxiedURL = [NSURL URLWithString:proxiedURLString];
+
+    [self logWithFormat:@"[OCSPCache] updated OCSP URL %@ to %@", url, proxiedURL];
+
+    return proxiedURL;
+}
+
 @end

+ 0 - 30
MobileLibrary/iOS/SampleApps/Common/OCSP.h

@@ -1,30 +0,0 @@
-//
-//  OCSP.h
-//  TunneledWebRequest
-//
-/*
- Licensed under Creative Commons Zero (CC0).
- https://creativecommons.org/publicdomain/zero/1.0/
- */
-
-
-// NOTE: this file is shared by TunneledWebRequest and TunneledWebView
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface OCSP : NSObject
-
-/*
- * Check in SecTrustRef (X.509 cert) for Online Certificate Status Protocol (1.3.6.1.5.5.7.48.1)
- * authority information access method. This is found in the
- * Certificate Authority Information Access (1.3.6.1.5.5.7.1.1) X.509v3 extension.
- *
- * X.509 Authority Information Access: https://tools.ietf.org/html/rfc2459#section-4.2.2.1
- */
-+ (NSArray<NSURL*>*_Nullable)ocspURLs:(SecTrustRef)secTrustRef error:(NSError**)error;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 232
MobileLibrary/iOS/SampleApps/Common/OCSP.m

@@ -1,232 +0,0 @@
-//
-//  OCSP.m
-//  TunneledWebRequest
-//
-/*
- Licensed under Creative Commons Zero (CC0).
- https://creativecommons.org/publicdomain/zero/1.0/
- */
-
-
-// NOTE: this file is shared by TunneledWebRequest and TunneledWebView
-
-#import "OCSP.h"
-#import "openssl/ocsp.h"
-#import "openssl/safestack.h"
-#import "openssl/x509.h"
-#import "openssl/x509v3.h"
-
-NSErrorDomain _Nonnull const OCSPErrorDomain = @"OCSPErrorDomain";
-
-typedef NS_ERROR_ENUM(OCSPErrorDomain, OCSPErrorCode) {
-    OCSPErrorCodeUnknown = -1,
-    OCSPErrorCodeInvalidNumCerts = 1,
-    OCSPErrorCodeNoLeafCert,
-    OCSPErrorCodeNoIssuerCert,
-    OCSPErrorCodeNoOCSPURLs,
-    OCSPErrorCodeEVPAllocFailed,
-    OCSPErrorCodeCertToIdFailed,
-    OCSPErrorCodeReqAllocFailed,
-    OCSPErrorCodeAddCertsToReqFailed,
-    OCSPErrorCodeFailedToSerializeOCSPReq,
-    OCSPErrorCodeConstructedInvalidURL
-};
-
-@implementation OCSP
-
-+ (NSArray<NSURL*>*_Nullable)ocspURLs:(SecTrustRef)secTrustRef error:(NSError**)error {
-    
-    NSMutableArray <void(^)(void)> *cleanup = [[NSMutableArray alloc] init];
-    
-    CFIndex certificateCount = SecTrustGetCertificateCount(secTrustRef);
-    if (certificateCount < 2) {
-        NSString *errorString = [NSString stringWithFormat:@"Expected 2 or more certificates "
-                                                            "(at least leaf and issuer) in the "
-                                                            "trust chain but only found %ld",
-                                                           (long)certificateCount];
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeInvalidNumCerts
-                                 userInfo:@{NSLocalizedDescriptionKey:errorString}];
-        return nil;
-    }
-    
-    X509 *leaf = [OCSP certAtIndex:secTrustRef withIndex:0];
-    if (leaf == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeNoLeafCert
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to get leaf "
-                                                                       "certficate"}];
-        return nil;
-    }
-    
-    [cleanup addObject:^(){
-        X509_free(leaf);
-    }];
-    
-    X509 *issuer = [OCSP certAtIndex:secTrustRef withIndex:1];
-    if (issuer == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeNoIssuerCert
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to get issuer "
-                                                                       "certificate"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    [cleanup addObject:^(){
-        X509_free(issuer);
-    }];
-    
-    NSArray<NSString*>* ocspURLs = [OCSP OCSPURLs:leaf];
-    if ([ocspURLs count] == 0) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeNoOCSPURLs
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Found 0 OCSP URLs in leaf "
-                                                                       "certificate"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    const EVP_MD *cert_id_md = EVP_sha1();
-    if (cert_id_md == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeEVPAllocFailed
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to allocate new EVP "
-                                                                       "sha1"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    OCSP_CERTID *id_t = OCSP_cert_to_id(cert_id_md, leaf, issuer);
-    if (id_t == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeCertToIdFailed
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to create "
-                                                                       "OCSP_CERTID structure"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    // Construct OCSP request
-    //
-    // https://www.ietf.org/rfc/rfc2560.txt
-    //
-    // An OCSP request using the GET method is constructed as follows:
-    //
-    // GET {url}/{url-encoding of base-64 encoding of the DER encoding of
-    //	   the OCSPRequest}
-    
-    OCSP_REQUEST *req = OCSP_REQUEST_new();
-    if (req == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeReqAllocFailed
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to allocate new "
-                                                                       "OCSP request"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    [cleanup addObject:^(){
-        OCSP_REQUEST_free(req);
-    }];
-    
-    if (OCSP_request_add0_id(req, id_t) == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeAddCertsToReqFailed
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to add certs to "
-                                                                       "OCSP request"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    unsigned char *ocspReq = NULL;
-    
-    int len = i2d_OCSP_REQUEST(req, &ocspReq);
-    
-    if (ocspReq == NULL) {
-        *error = [NSError errorWithDomain:OCSPErrorDomain
-                                     code:OCSPErrorCodeFailedToSerializeOCSPReq
-                                 userInfo:@{NSLocalizedDescriptionKey:@"Failed to serialize OCSP "
-                                                                       "request"}];
-        [OCSP execCleanupTasks:cleanup];
-        return nil;
-    }
-    
-    [cleanup addObject:^(){
-        free(ocspReq);
-    }];
-    
-    NSData *ocspReqData = [NSData dataWithBytes:ocspReq length:len];
-    NSString *encodedOCSPReqData = [ocspReqData base64EncodedStringWithOptions:kNilOptions];
-    NSString *escapedAndEncodedOCSPReqData = [encodedOCSPReqData
-                                              stringByAddingPercentEncodingWithAllowedCharacters:
-                                              NSCharacterSet.URLFragmentAllowedCharacterSet];
-    
-    NSMutableArray<NSURL*>* reqURLs = [[NSMutableArray alloc] initWithCapacity:[ocspURLs count]];
-    
-    for (NSString *ocspURL in ocspURLs) {
-        
-        NSString *reqURL = [NSString stringWithFormat:@"%@/%@",
-                                                      ocspURL,
-                                                      escapedAndEncodedOCSPReqData];
-        
-        NSURL *url = [NSURL URLWithString:reqURL];
-        if (url == nil) {
-            NSString *localizedDescription = [NSString stringWithFormat:@"Constructed invalid URL "
-                                                                         "for OCSP request: %@",
-                                                                        reqURL];
-            *error = [NSError errorWithDomain:OCSPErrorDomain
-                                         code:OCSPErrorCodeConstructedInvalidURL
-                                     userInfo:@{NSLocalizedDescriptionKey:localizedDescription}];
-            [OCSP execCleanupTasks:cleanup];
-            return nil;
-        }
-        
-        [reqURLs addObject:url];
-    }
-    
-    [OCSP execCleanupTasks:cleanup];
-    
-    return reqURLs;
-}
-
-#pragma mark - Internal Helpers
-
-+ (X509*)certAtIndex:(SecTrustRef)trust withIndex:(int)index {
-    if (SecTrustGetCertificateCount(trust) < index) {
-        return nil;
-    }
-    
-    SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, index);
-    
-    NSData *data = (__bridge_transfer NSData *)SecCertificateCopyData(cert);
-    
-    const unsigned char *p = [data bytes];
-    X509 *x = d2i_X509(NULL, &p, [data length]);
-    
-    return x;
-}
-
-+ (NSArray<NSString*>*)OCSPURLs:(X509*)x {
-    STACK_OF(OPENSSL_STRING) *ocspURLs = X509_get1_ocsp(x);
-    
-    NSMutableArray *URLs = [[NSMutableArray alloc]
-                            initWithCapacity:sk_OPENSSL_STRING_num(ocspURLs)];
-    
-    for (int i = 0; i < sk_OPENSSL_STRING_num(ocspURLs); i++) {
-        [URLs addObject:[NSString stringWithCString:sk_OPENSSL_STRING_value(ocspURLs, i)
-                                           encoding:NSUTF8StringEncoding]];
-    }
-    
-    sk_OPENSSL_STRING_free(ocspURLs);
-    
-    return URLs;
-}
-
-+ (void)execCleanupTasks:(NSArray<void(^)(void)> *)cleanupTasks {
-    for (void (^cleanupTask)(void) in cleanupTasks) {
-        cleanupTask();
-    }
-}
-
-@end

+ 0 - 24
MobileLibrary/iOS/SampleApps/Common/URLEncode.h

@@ -1,24 +0,0 @@
-//
-//  URLEncode.h
-//  TunneledWebRequest
-//
-/*
- Licensed under Creative Commons Zero (CC0).
- https://creativecommons.org/publicdomain/zero/1.0/
- */
-
-
-// NOTE: this file is shared by TunneledWebRequest and TunneledWebView
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface URLEncode : NSObject
-
-// See comment in URLEncode.m
-+ (NSString*__nullable)encode:(NSString*)url;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 0 - 39
MobileLibrary/iOS/SampleApps/Common/URLEncode.m

@@ -1,39 +0,0 @@
-//
-//  URLEncode.m
-//  TunneledWebRequest
-//
-/*
- Licensed under Creative Commons Zero (CC0).
- https://creativecommons.org/publicdomain/zero/1.0/
- */
-
-
-// NOTE: this file is shared by TunneledWebRequest and TunneledWebView
-
-#import "URLEncode.h"
-
-@implementation URLEncode
-
-// Encode all reserved characters. See: https://stackoverflow.com/a/34788364.
-//
-// From RFC 3986 (https://www.ietf.org/rfc/rfc3986.txt):
-//
-//   2.3.  Unreserved Characters
-//
-//   Characters that are allowed in a URI but do not have a reserved
-//   purpose are called unreserved.  These include uppercase and lowercase
-//   letters, decimal digits, hyphen, period, underscore, and tilde.
-//
-//   unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
-+ (NSString*)encode:(NSString*)url {
-    NSCharacterSet *queryParamCharsAllowed = [NSCharacterSet
-                                              characterSetWithCharactersInString:
-                                              @"abcdefghijklmnopqrstuvwxyz"
-                                              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                                              "0123456789"
-                                              "-._~"];
-
-    return [url stringByAddingPercentEncodingWithAllowedCharacters:queryParamCharsAllowed];
-}
-
-@end

+ 2 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/Podfile

@@ -3,5 +3,7 @@ platform :ios, '10.0'
 target 'TunneledWebRequest' do
     pod 'OpenSSL-Universal', '1.0.2.17'
     pod 'PsiphonTunnel', :git => 'https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git'
+    pod 'OCSPCache', :git => "https://github.com/Psiphon-Labs/OCSPCache.git", :commit => '647c7b0'
+    #pod 'OCSPCache', :path => "../../../../../OCSPCache/"
 end
 

+ 14 - 1
MobileLibrary/iOS/SampleApps/TunneledWebRequest/Podfile.lock

@@ -1,24 +1,37 @@
 PODS:
+  - OCSPCache (0.1.0):
+    - OpenSSL-Universal (= 1.0.2.17)
+    - ReactiveObjC (= 3.1.1)
   - OpenSSL-Universal (1.0.2.17)
   - PsiphonTunnel (2.0.2)
+  - ReactiveObjC (3.1.1)
 
 DEPENDENCIES:
+  - OCSPCache (from `https://github.com/Psiphon-Labs/OCSPCache.git`, commit `647c7b0`)
   - OpenSSL-Universal (= 1.0.2.17)
   - PsiphonTunnel (from `https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git`)
 
 EXTERNAL SOURCES:
+  OCSPCache:
+    :commit: 647c7b0
+    :git: https://github.com/Psiphon-Labs/OCSPCache.git
   PsiphonTunnel:
     :git: https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git
 
 CHECKOUT OPTIONS:
+  OCSPCache:
+    :commit: 647c7b0
+    :git: https://github.com/Psiphon-Labs/OCSPCache.git
   PsiphonTunnel:
     :commit: c9af3bab93637163e117de9d1e77435baa7646c0
     :git: https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git
 
 SPEC CHECKSUMS:
+  OCSPCache: 96237607aa9f77ba2fd9119e383b9fb1ef41cfa2
   OpenSSL-Universal: ff04c2e6befc3f1247ae039e60c93f76345b3b5a
   PsiphonTunnel: 0c3f8677e4b26316beba57df78ed9c75634ce091
+  ReactiveObjC: 011caa393aa0383245f2dcf9bf02e86b80b36040
 
-PODFILE CHECKSUM: a17e050a23b29cf7dab9ff0e4bce879f3d239fac
+PODFILE CHECKSUM: 7ea04fcb82030754b54bcd4129e9c93671a6c15d
 
 COCOAPODS: 1.4.0

+ 0 - 12
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.pbxproj

@@ -16,8 +16,6 @@
 		6626590E1DCB8CF400872F6C /* TunneledWebRequestUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */; };
 		6682D90E1EB1334000329958 /* psiphon-embedded-server-entries.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6682D90D1EB1334000329958 /* psiphon-embedded-server-entries.txt */; };
 		6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 6688DBB51DCD684B00721A9E /* psiphon-config.json */; };
-		CEFA7ECE2294BB410078E41E /* OCSP.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7ECC2294BB410078E41E /* OCSP.m */; };
-		CEFA7ED12294BB860078E41E /* URLEncode.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7ECF2294BB860078E41E /* URLEncode.m */; };
 		CEFA7EDD2295A5530078E41E /* AuthURLSessionTaskDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7EDB2295A5530078E41E /* AuthURLSessionTaskDelegate.m */; };
 		DEB1E38A2E15C48277D2E61E /* libPods-TunneledWebRequest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37F726750F0082A9B17447DC /* libPods-TunneledWebRequest.a */; };
 /* End PBXBuildFile section */
@@ -70,10 +68,6 @@
 		6688DBB51DCD684B00721A9E /* psiphon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "psiphon-config.json"; sourceTree = "<group>"; };
 		6BA09789075034B337A791DA /* Pods-TunneledWebRequest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TunneledWebRequest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TunneledWebRequest/Pods-TunneledWebRequest.debug.xcconfig"; sourceTree = "<group>"; };
 		CEFA7EB82294A9BB0078E41E /* TunneledWebRequest-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TunneledWebRequest-Bridging-Header.h"; sourceTree = "<group>"; };
-		CEFA7ECC2294BB410078E41E /* OCSP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCSP.m; path = ../../../Common/OCSP.m; sourceTree = "<group>"; };
-		CEFA7ECD2294BB410078E41E /* OCSP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCSP.h; path = ../../../Common/OCSP.h; sourceTree = "<group>"; };
-		CEFA7ECF2294BB860078E41E /* URLEncode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = URLEncode.m; path = ../../Common/URLEncode.m; sourceTree = "<group>"; };
-		CEFA7ED02294BB860078E41E /* URLEncode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = URLEncode.h; path = ../../Common/URLEncode.h; sourceTree = "<group>"; };
 		CEFA7EDB2295A5530078E41E /* AuthURLSessionTaskDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AuthURLSessionTaskDelegate.m; path = ../../../Common/AuthURLSessionTaskDelegate.m; sourceTree = "<group>"; };
 		CEFA7EDC2295A5530078E41E /* AuthURLSessionTaskDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AuthURLSessionTaskDelegate.h; path = ../../../Common/AuthURLSessionTaskDelegate.h; sourceTree = "<group>"; };
 		FC00C7E45B17A3FDBDC1BF85 /* Pods-TunneledWebRequest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TunneledWebRequest.release.xcconfig"; path = "Pods/Target Support Files/Pods-TunneledWebRequest/Pods-TunneledWebRequest.release.xcconfig"; sourceTree = "<group>"; };
@@ -141,8 +135,6 @@
 			children = (
 				662658ED1DCB8CF300872F6C /* AppDelegate.swift */,
 				662658EF1DCB8CF300872F6C /* ViewController.swift */,
-				CEFA7ED02294BB860078E41E /* URLEncode.h */,
-				CEFA7ECF2294BB860078E41E /* URLEncode.m */,
 				CEFA7EC22294B2D20078E41E /* URLSessionDelegate */,
 				662658F11DCB8CF300872F6C /* Main.storyboard */,
 				662658F41DCB8CF300872F6C /* Assets.xcassets */,
@@ -186,8 +178,6 @@
 			children = (
 				CEFA7EDC2295A5530078E41E /* AuthURLSessionTaskDelegate.h */,
 				CEFA7EDB2295A5530078E41E /* AuthURLSessionTaskDelegate.m */,
-				CEFA7ECD2294BB410078E41E /* OCSP.h */,
-				CEFA7ECC2294BB410078E41E /* OCSP.m */,
 			);
 			path = URLSessionDelegate;
 			sourceTree = "<group>";
@@ -391,11 +381,9 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CEFA7ECE2294BB410078E41E /* OCSP.m in Sources */,
 				662658F01DCB8CF300872F6C /* ViewController.swift in Sources */,
 				CEFA7EDD2295A5530078E41E /* AuthURLSessionTaskDelegate.m in Sources */,
 				662658EE1DCB8CF300872F6C /* AppDelegate.swift in Sources */,
-				CEFA7ED12294BB860078E41E /* URLEncode.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 85 - 44
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/AppDelegate.swift

@@ -24,7 +24,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
     // Delegate for handling certificate validation.
     var authURLSessionTaskDelegate: AuthURLSessionTaskDelegate =
-        AuthURLSessionTaskDelegate.init(logger: {print("[AuthURLSessionTaskDelegate]: ", $0)},
+        AuthURLSessionTaskDelegate.init(logger: {print("[AuthURLSessionTaskDelegate]:", $0)},
                                         andLocalHTTPProxyPort: 0)
 
     @objc public class func sharedDelegate() -> AppDelegate {
@@ -95,7 +95,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     ///   - completion: A callback function that will received the string obtained
     ///     from the request, or nil if there's an error.
     /// * returns: The string obtained from the request, or nil if there's an error.
-    func makeRequestViaUrlSessionProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
+    func makeRequestViaUrlSessionProxy(_ url: String, completion: @escaping (_ result: String?, _ error: String?) -> ()) {
         let socksProxyPort = self.socksProxyPort
         assert(socksProxyPort > 0)
 
@@ -104,6 +104,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         let config = URLSessionConfiguration.ephemeral
         config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
         config.connectionProxyDictionary = [AnyHashable: Any]()
+        config.timeoutIntervalForRequest = 60 * 5
 
         // Enable and set the SOCKS proxy values.
         config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxy as String] = 1
@@ -124,24 +125,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         let task = session.dataTask(with: request) {
             (data: Data?, response: URLResponse?, error: Error?) in
             if error != nil {
-                NSLog("Client-side error in request to \(url): \(String(describing: error))")
+                let errorString = "Client-side error in request to \(url): \(String(describing: error))"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
             if data == nil {
-                NSLog("Data from request to \(url) is nil")
+                let errorString = "Data from request to \(url) is nil"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
             let httpResponse = response as? HTTPURLResponse
             if httpResponse?.statusCode != 200 {
-                NSLog("Server-side error in request to \(url): \(String(describing: httpResponse))")
+                let errorString = "Server-side error in request to \(url): \(String(describing: httpResponse))"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
@@ -154,7 +158,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             session.invalidateAndCancel()
 
             // Invoke the callback with the result.
-            completion(stringData)
+            completion(stringData, nil)
         }
 
         // Start the request task.
@@ -169,7 +173,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     ///   - completion: A callback function that will received the string obtained
     ///     from the request, or nil if there's an error.
     /// * returns: The string obtained from the request, or nil if there's an error.
-    func makeRequestViaUrlProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
+    func makeRequestViaUrlProxy(_ url: String, completion: @escaping (_ result: String?, _ error: String?) -> ()) {
         let httpProxyPort = self.httpProxyPort
         assert(httpProxyPort > 0)
 
@@ -181,24 +185,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         let task = URLSession.shared.dataTask(with: URL(string: proxiedURL)!) {
             (data: Data?, response: URLResponse?, error: Error?) in
             if error != nil {
-                NSLog("Client-side error in request to \(url): \(String(describing: error))")
+                let errorString = "Client-side error in request to \(url): \(String(describing: error))"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
             if data == nil {
-                NSLog("Data from request to \(url) is nil")
+                let errorString = "Data from request to \(url) is nil"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
             let httpResponse = response as? HTTPURLResponse
             if httpResponse?.statusCode != 200 {
-                NSLog("Server-side error in request to \(url): \(String(describing: httpResponse))")
+                let errorString = "Server-side error in request to \(url): \(String(describing: httpResponse))"
+                NSLog(errorString)
                 // Invoke the callback indicating error.
-                completion(nil)
+                completion(nil, errorString)
                 return
             }
 
@@ -208,7 +215,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             let stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
 
             // Invoke the callback with the result.
-            completion(stringData)
+            completion(stringData, nil)
         }
 
         // Start the request task.
@@ -267,44 +274,78 @@ extension AppDelegate: TunneledAppDelegate {
             // First we'll make a "what is my IP" request via makeRequestViaUrlSessionProxy().
             let url = "https://freegeoip.app/json/"
             self.makeRequestViaUrlSessionProxy(url) {
-                (_ result: String?) in
-
-                if result == nil {
-                    NSLog("Failed to get \(url)")
-                    return
-                }
+                (_ result: String?, _ error: String?) in
+
+                if let errorString = error?.replacingOccurrences(of: ",", with: ",\n  ")
+                                           .replacingOccurrences(of: "{", with: "{\n  ")
+                                           .replacingOccurrences(of: "}", with: "\n}")
+                {
+                    DispatchQueue.main.sync {
+                        // Load the result into the view.
+                        let mainView = self.window?.rootViewController as! ViewController
+                        mainView.appendToView("""
+                        Error from \(url):\n\n
+                        \(errorString)\n\n
+                        Using makeRequestViaUrlSessionProxy.\n\n
+                        Check logs for error.
+                        """)
+                    }
+                } else {
+                    if result == nil {
+                        NSLog("Failed to get \(url)")
+                        return
+                    }
 
-                // Do a little pretty-printing.
-                let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
-                    .replacingOccurrences(of: "{", with: "{\n  ")
-                    .replacingOccurrences(of: "}", with: "\n}")
+                    // Do a little pretty-printing.
+                    let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
+                                              .replacingOccurrences(of: "{", with: "{\n  ")
+                                              .replacingOccurrences(of: "}", with: "\n}")
 
-                DispatchQueue.main.sync {
-                    // Load the result into the view.
-                    let mainView = self.window?.rootViewController as! ViewController
-                    mainView.appendToView("Result from \(url):\n\(prettyResult!)")
+                    DispatchQueue.main.sync {
+                        // Load the result into the view.
+                        let mainView = self.window?.rootViewController as! ViewController
+                        mainView.appendToView("Result from \(url):\n\(prettyResult!)")
+                    }
                 }
 
                 // Then we'll make a different "what is my IP" request via makeRequestViaUrlProxy().
                 DispatchQueue.global(qos: .default).async {
                     let url = "https://ifconfig.co/json"
                     self.makeRequestViaUrlProxy(url) {
-                        (_ result: String?) in
+                        (_ result: String?, _ error: String?) in
 
-                        if result == nil {
-                            NSLog("Failed to get \(url)")
-                            return
-                        }
-
-                        // Do a little pretty-printing.
-                        let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
+                        if let errorString = error?.replacingOccurrences(of: ",", with: ",\n  ")
                             .replacingOccurrences(of: "{", with: "{\n  ")
                             .replacingOccurrences(of: "}", with: "\n}")
-
-                        DispatchQueue.main.sync {
-                            // Load the result into the view.
-                            let mainView = self.window?.rootViewController as! ViewController
-                            mainView.appendToView("Result from \(url):\n\(prettyResult!)")
+                        {
+                            DispatchQueue.main.sync {
+                                // Load the result into the view.
+                                let mainView = self.window?.rootViewController as! ViewController
+                                mainView.appendToView("""
+                                    Error from \(url):\n\n
+                                    \(errorString)\n\n
+                                    Using makeRequestViaUrlProxy.\n\n
+                                    Check logs for error.
+                                    """)
+                            }
+                            return
+                        } else {
+
+                            if result == nil {
+                                NSLog("Failed to get \(url)")
+                                return
+                            }
+
+                            // Do a little pretty-printing.
+                            let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
+                                .replacingOccurrences(of: "{", with: "{\n  ")
+                                .replacingOccurrences(of: "}", with: "\n}")
+
+                            DispatchQueue.main.sync {
+                                // Load the result into the view.
+                                let mainView = self.window?.rootViewController as! ViewController
+                                mainView.appendToView("Result from \(url):\n\(prettyResult!)")
+                            }
                         }
 
                         // We'll leave the tunnel open for when we want to make

+ 4 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Info.plist

@@ -24,8 +24,12 @@
 	<true/>
 	<key>NSAppTransportSecurity</key>
 	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
 		<key>NSExceptionDomains</key>
 		<dict>
+			<key>freegeoip.app</key>
+			<string></string>
 			<key>ip-api.com</key>
 			<string>YES</string>
 			<key>ipinfo.io</key>

+ 1 - 1
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/TunneledWebRequest-Bridging-Header.h

@@ -3,4 +3,4 @@
 //
 
 #import "AuthURLSessionTaskDelegate.h"
-#import "URLEncode.h"
+#import "OCSPURLEncode.h"

+ 1 - 3
MobileLibrary/iOS/SampleApps/TunneledWebView/External/JiveAuthenticatingHTTPProtocol/JAHPAuthenticatingHTTPProtocol.m

@@ -52,9 +52,7 @@
 #import "JAHPQNSURLSessionDemux.h"
 
 #import "AuthURLSessionTaskDelegate.h"
-#import "OCSP.h"
 #import "TunneledWebView-Swift.h"
-#import "URLEncode.h"
 
 // I use the following typedef to keep myself sane in the face of the wacky
 // Objective-C block syntax.
@@ -798,7 +796,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
         // Delegate for handling certificate validation.
         // Makes OCSP requests through local HTTP proxy.
         AuthURLSessionTaskDelegate *authHandler = [[AppDelegate sharedDelegate] authURLSessionTaskDelegate];
-        
+
         [authHandler URLSession:session
                                 task:task
                  didReceiveChallenge:challenge

+ 2 - 0
MobileLibrary/iOS/SampleApps/TunneledWebView/Podfile

@@ -3,5 +3,7 @@ platform :ios, '10.0'
 target 'TunneledWebView' do
     pod 'OpenSSL-Universal', '1.0.2.17'
     pod 'PsiphonTunnel', :git => 'https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git'
+    pod 'OCSPCache', :git => "https://github.com/Psiphon-Labs/OCSPCache.git", :commit => '647c7b0'
+    #pod 'OCSPCache', :path => "../../../../../OCSPCache/"
 end
 

+ 14 - 1
MobileLibrary/iOS/SampleApps/TunneledWebView/Podfile.lock

@@ -1,24 +1,37 @@
 PODS:
+  - OCSPCache (0.1.0):
+    - OpenSSL-Universal (= 1.0.2.17)
+    - ReactiveObjC (= 3.1.1)
   - OpenSSL-Universal (1.0.2.17)
   - PsiphonTunnel (2.0.2)
+  - ReactiveObjC (3.1.1)
 
 DEPENDENCIES:
+  - OCSPCache (from `https://github.com/Psiphon-Labs/OCSPCache.git`, commit `647c7b0`)
   - OpenSSL-Universal (= 1.0.2.17)
   - PsiphonTunnel (from `https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git`)
 
 EXTERNAL SOURCES:
+  OCSPCache:
+    :commit: 647c7b0
+    :git: https://github.com/Psiphon-Labs/OCSPCache.git
   PsiphonTunnel:
     :git: https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git
 
 CHECKOUT OPTIONS:
+  OCSPCache:
+    :commit: 647c7b0
+    :git: https://github.com/Psiphon-Labs/OCSPCache.git
   PsiphonTunnel:
     :commit: c9af3bab93637163e117de9d1e77435baa7646c0
     :git: https://github.com/Psiphon-Labs/psiphon-tunnel-core-iOS-library.git
 
 SPEC CHECKSUMS:
+  OCSPCache: 96237607aa9f77ba2fd9119e383b9fb1ef41cfa2
   OpenSSL-Universal: ff04c2e6befc3f1247ae039e60c93f76345b3b5a
   PsiphonTunnel: 0c3f8677e4b26316beba57df78ed9c75634ce091
+  ReactiveObjC: 011caa393aa0383245f2dcf9bf02e86b80b36040
 
-PODFILE CHECKSUM: 96ed660616525565a4c1e99a04e1c72a9474883d
+PODFILE CHECKSUM: 8a870dda0fa972bb0b856484a40170d5bb9da4c7
 
 COCOAPODS: 1.4.0

+ 0 - 12
MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView.xcodeproj/project.pbxproj

@@ -21,8 +21,6 @@
 		6626590E1DCB8CF400872F6C /* TunneledWebViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6626590D1DCB8CF400872F6C /* TunneledWebViewUITests.swift */; };
 		6682D90E1EB1334000329958 /* psiphon-embedded-server-entries.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6682D90D1EB1334000329958 /* psiphon-embedded-server-entries.txt */; };
 		6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 6688DBB51DCD684B00721A9E /* psiphon-config.json */; };
-		CEFA7ECB2294BB080078E41E /* OCSP.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7ECA2294BB080078E41E /* OCSP.m */; };
-		CEFA7ED42294BB8F0078E41E /* URLEncode.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7ED22294BB8F0078E41E /* URLEncode.m */; };
 		CEFA7ED72294BC0C0078E41E /* AuthURLSessionTaskDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFA7ED62294BC0C0078E41E /* AuthURLSessionTaskDelegate.m */; };
 		DDFD23795085E5852A8F4DD5 /* libPods-TunneledWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E472F80E34E361EB72B2FD0C /* libPods-TunneledWebView.a */; };
 /* End PBXBuildFile section */
@@ -84,10 +82,6 @@
 		6688DBB51DCD684B00721A9E /* psiphon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "psiphon-config.json"; sourceTree = "<group>"; };
 		76C8CF5D2CF9F4228B9CD56E /* Pods-TunneledWebView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TunneledWebView.release.xcconfig"; path = "Pods/Target Support Files/Pods-TunneledWebView/Pods-TunneledWebView.release.xcconfig"; sourceTree = "<group>"; };
 		85795C6590EED64B7A6684AA /* Pods-TunneledWebView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TunneledWebView.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TunneledWebView/Pods-TunneledWebView.debug.xcconfig"; sourceTree = "<group>"; };
-		CEFA7EC92294BB080078E41E /* OCSP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCSP.h; path = ../../../Common/OCSP.h; sourceTree = "<group>"; };
-		CEFA7ECA2294BB080078E41E /* OCSP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCSP.m; path = ../../../Common/OCSP.m; sourceTree = "<group>"; };
-		CEFA7ED22294BB8F0078E41E /* URLEncode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = URLEncode.m; path = ../../../Common/URLEncode.m; sourceTree = "<group>"; };
-		CEFA7ED32294BB8F0078E41E /* URLEncode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = URLEncode.h; path = ../../../Common/URLEncode.h; sourceTree = "<group>"; };
 		CEFA7ED52294BC0C0078E41E /* AuthURLSessionTaskDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AuthURLSessionTaskDelegate.h; path = ../../../Common/AuthURLSessionTaskDelegate.h; sourceTree = "<group>"; };
 		CEFA7ED62294BC0C0078E41E /* AuthURLSessionTaskDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AuthURLSessionTaskDelegate.m; path = ../../../Common/AuthURLSessionTaskDelegate.m; sourceTree = "<group>"; };
 		E472F80E34E361EB72B2FD0C /* libPods-TunneledWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-TunneledWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -132,10 +126,6 @@
 				4E0CA9651FDE554B00B48BCA /* JAHPCanonicalRequest.m */,
 				4E0CA9661FDE554B00B48BCA /* JAHPQNSURLSessionDemux.h */,
 				4E0CA9671FDE554B00B48BCA /* JAHPQNSURLSessionDemux.m */,
-				CEFA7EC92294BB080078E41E /* OCSP.h */,
-				CEFA7ECA2294BB080078E41E /* OCSP.m */,
-				CEFA7ED32294BB8F0078E41E /* URLEncode.h */,
-				CEFA7ED22294BB8F0078E41E /* URLEncode.m */,
 			);
 			name = JiveAuthenticatingHTTPProtocol;
 			path = External/JiveAuthenticatingHTTPProtocol;
@@ -422,8 +412,6 @@
 				4E0CA9681FDE554B00B48BCA /* JAHPAuthenticatingHTTPProtocol.m in Sources */,
 				CEFA7ED72294BC0C0078E41E /* AuthURLSessionTaskDelegate.m in Sources */,
 				4E0CA9691FDE554B00B48BCA /* JAHPCacheStoragePolicy.m in Sources */,
-				CEFA7ECB2294BB080078E41E /* OCSP.m in Sources */,
-				CEFA7ED42294BB8F0078E41E /* URLEncode.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 1 - 2
MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView/AppDelegate.swift

@@ -24,7 +24,7 @@ import PsiphonTunnel
 
     // Delegate for handling certificate validation.
     @objc public var authURLSessionTaskDelegate: AuthURLSessionTaskDelegate =
-        AuthURLSessionTaskDelegate.init(logger: {print("[AuthURLSessionTaskDelegate]: ", $0)},
+        AuthURLSessionTaskDelegate.init(logger: {print("[AuthURLSessionTaskDelegate]:", $0)},
                                          andLocalHTTPProxyPort: 0)
     
     @objc public class func sharedDelegate() -> AppDelegate {
@@ -188,5 +188,4 @@ extension AppDelegate: TunneledAppDelegate {
             self.httpProxyPort = port
         }
     }
-
 }

+ 3 - 1
MobileLibrary/iOS/SampleApps/TunneledWebView/TunneledWebView/Info.plist

@@ -25,9 +25,11 @@
 	<key>NSAppTransportSecurity</key>
 	<dict>
 		<key>NSAllowsArbitraryLoads</key>
-		<false/>
+		<true/>
 		<key>NSExceptionDomains</key>
 		<dict>
+			<key>freegeoip.app</key>
+			<string></string>
 			<key>ifconfig.co</key>
 			<string>YES</string>
 			<key>ip-api.com</key>

+ 9 - 2
MobileLibrary/iOS/USAGE.md

@@ -55,11 +55,18 @@ When run in a simulator, there may be errors shown in the device log. This does
 
 On iOS, requests which use HTTPS can trigger remote certificate revocation checks. Currently, the OS does this automatically by making plaintext HTTP [OCSP requests](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol).
 
-Unfortunately, these OCSP requests do not respect [connection proxy dictionary settings](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411499-connectionproxydictionary?language=objc) or [NSURLProtocol](https://developer.apple.com/documentation/foundation/nsurlprotocol) subclasses; they are likely performed out of process. The payload in the plaintext OCSP requests leaks the identity of the certificate that is being validated. The risk is that an observer can [map the certificate's serial number back to the certificate](https://github.com/OnionBrowser/OnionBrowser/issues/178#issue-437802301) to find more information about the website or server being accessed.
+Unfortunately, these OCSP requests do not respect [connection proxy dictionary settings](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411499-connectionproxydictionary?language=objc) or [NSURLProtocol](https://developer.apple.com/documentation/foundation/nsurlprotocol) subclasses; they are likely performed out of process. The payload in each plaintext OCSP request leaks the identity of the certificate that is being validated.
+
+The risk is that an observer can [map the certificate's serial number back to the certificate](https://github.com/OnionBrowser/OnionBrowser/issues/178#issue-437802301) to find more information about the website or server being accessed.
 
 ### Fix
 
-A fix has been implemented in both sample apps: [TunneledWebRequest](SampleApps/TunneledWebRequest) and [TunneledWebView](SampleApps/TunneledWebView). This is done by implementing [URLSession:task:didReceiveChallenge:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate/1411595-urlsession?language=objc) of the [NSURLSessionTaskDelegate](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate) protocol. This allows us to perform OCSP requests manually and ensure that they are tunneled. See the comments in [SampleApps/Common/AuthURLSessionTaskDelegate.h](SampleApps/Common/AuthURLSessionTaskDelegate.h) and both sample apps for a reference implementation.
+A fix has been implemented in both sample apps: [TunneledWebRequest](SampleApps/TunneledWebRequest) and [TunneledWebView](SampleApps/TunneledWebView). This is done by implementing [URLSession:task:didReceiveChallenge:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate/1411595-urlsession?language=objc) of the [NSURLSessionTaskDelegate](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate) protocol; this allows us to control how revocation checkings is performed.
+
+This allows us to perform OCSP requests manually and ensure that they are tunneled. See the comments in [SampleApps/Common/AuthURLSessionTaskDelegate.h](SampleApps/Common/AuthURLSessionTaskDelegate.h) and both sample apps for a reference implementation.
+
+AuthURLSessionTaskDelegate relies on [OCSPCache](https://github.com/Psiphon-Labs/OCSPCache) for constructing OCSP requests and caching OCSP responses.
+
 
 ## Proxying a web view