cordova-plugin-crypto-file icon indicating copy to clipboard operation
cordova-plugin-crypto-file copied to clipboard

iOS Support

Open FawadNL opened this issue 6 years ago • 18 comments

Hi,

It is not working with iOS. The files are getting encrypted properly, but when I run the app via Xcode, the app gets stuck at splashscreen.

FawadNL avatar Jan 23 '19 14:01 FawadNL

Hello,

Are you getting any error? Also what are you getting in the log?

PeterHdd avatar Jan 23 '19 16:01 PeterHdd

Hi,

I am not getting any error in Xcode log, but will see the Safari developer console for JS error. Did the plugin work at your end?

FawadNL avatar Jan 24 '19 09:01 FawadNL

Nope, I'm gonna try and fix it this week.

PeterHdd avatar Jan 24 '19 09:01 PeterHdd

I only see this error in my Safari inspect window - SyntaxError: No identifiers allowed directly after numeric literal

FawadNL avatar Jan 24 '19 15:01 FawadNL

@PeterHdd I am also facing the same issue.

an-rahulpandey avatar Jan 25 '19 06:01 an-rahulpandey

yes IOS part is not working, only android part until now. Still checking the ios part.

PeterHdd avatar Jan 25 '19 06:01 PeterHdd

Ok, let me know if you need any help in debugging.

an-rahulpandey avatar Jan 25 '19 06:01 an-rahulpandey

If you are able to figure it out also, please commit it, thank you!

PeterHdd avatar Jan 25 '19 06:01 PeterHdd

OK, so I have been testing and trying to make it work on IOS, but its not working. So, I need either of you to help me(@FawadNL @an-rahulpandey ) as my knowledge in iOS or objective C is null actually.

So here is the thing, the iOS part has not been updated for 4 years, so basically I need to fix everything. To explain how to make it work:

First since ionic webview version 2.0+ works on localhost:8080 server, then the plugin crypto-file needs to be also on localhost:8080 to work.

Therefore in the javascript part, I did the following if it is platform iOS (it works):

        if (platform == 'ios') {
            var pluginDir;
            console.log(platformInfo);
            var iosProject = platformInfo.locations.xcodeCordovaProj;
            pluginDir        = path.join(iosProject, 'Plugins',context.opts.plugin.id);
            replaceCryptKey_ios(pluginDir, key, iv);

            var cfg   = new ConfigParser(platformInfo.projectConfig.path);
            var port = cfg.getGlobalPreference("cryptoPort");
            if( port == '')
            {
                cfg.doc.getroot().getchildren().filter(function(child, idx, arr) {
                    return (child.tag == 'content');
                }).forEach(function(child) {
                    child.attrib.src = 'http://localhost:8080/' + child.attrib.src;
                });
            }
            else
            {
                cfg.doc.getroot().getchildren().filter(function(child, idx, arr) {
                    return (child.tag == 'content');
                }).forEach(function(child) {
                    child.attrib.src = 'http://localhost:' + port + '/' + child.attrib.src;
                });
            }
            cfg.write();
       }

cfg.write() will send http://localhost:8080/index.html and/or http://localhost:8080/cordova.js...etc... to the objective c code. So, basically http://localhost:8080/ is the www in the ionic app and it will send all the decrypted files to the objective c code. (Therefore the javascript part is correct).

Now, the file that needs to be modified is CDVCryptURLProtocol.m, here is what I did:

#import "CDVCryptURLProtocol.h"

#import <MobileCoreServices/MobileCoreServices.h>
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>


static NSString* const kCryptKey = @" ";
static NSString* const kCryptIv = @" ";

static int const kIncludeFileLength = 1;
static int const kExcludeFileLength = 0;
static NSString* const kIncludeFiles[] = { @"\\.(htm|html|js|css)$" };
static NSString* const kExcludeFiles[] = {  };
NSString *retrievePath;
NSString *wwwPath;
NSString *checkPath;


@implementation CDVCryptURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{

    NSLog(@"the url inside the init request: %@",theRequest);
    if ([self checkCryptFile:theRequest.URL]) {
        return YES;
    }
    
    return [super canInitWithRequest:theRequest];
}

- (void)startLoading
{
    NSURL* url  = self.request.URL;

    wwwPath = [[NSBundle mainBundle].resourcePath stringByAppendingString:@"/www"];
    NSString *urling    = [@"file://"stringByAppendingString:wwwPath];
    NSString *urlings   = [urling stringByAppendingString:checkPath];
    NSString *finalUrl  = [urlings stringByReplacingOccurrencesOfString:@" " withString:@"%20"];

    NSURL *urls = [[NSURL alloc] initWithString:finalUrl];
    url         = urls;

    //    if ([[self class] checkCryptFile:url]) {
    NSString *mimeType = [self getMimeType:url];
    NSError* error;
    NSString* content  = [[NSString alloc] initWithContentsOfFile:url.path encoding:NSUTF8StringEncoding error:&error];
    if(error)
    {
        NSLog(@"the error inside is: %@", error);
    }
    if (!error) {
        NSLog(@"Decrypt: %@",url);
        NSData* data = [self decryptAES256WithKey:kCryptKey iv:kCryptIv data:content];
        [self sendResponseWithResponseCode:200 data:data mimeType:mimeType];
    }

    
    [super startLoading];
}


+ (BOOL)checkCryptFile:(NSURL *)url {

    checkPath = [url.path stringByReplacingOccurrencesOfString:@"http://localhost:8080/" withString:@""];//index.html
    NSLog(@"the check path: %@", checkPath); //index.html
    
    if (![self hasMatch:checkPath regexArr:kIncludeFiles length:kIncludeFileLength]) {
        return NO;
    }
    if ([self hasMatch:checkPath regexArr:kExcludeFiles length:kExcludeFileLength]) {
        return NO;
    }
    
    return YES;
}

+ (BOOL)hasMatch:(NSString *)text regexArr:(NSString* const [])regexArr length:(int)length {
    for (int i = 0; i < length; i++) {
        NSString* const regex = regexArr[i];
        if ([self isMatch:text pattern:regex]) {
            return YES;
        }
    }
    return NO;
}

+ (BOOL)isMatch:(NSString *)text pattern:(NSString *)pattern {
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
    if (error) {
        return NO;
    }
    if ([regex firstMatchInString:text options:0 range:NSMakeRange(0, text.length)]) {
        return YES;
    }
    return NO;
}

- (NSString*)getMimeType:(NSURL *)url
{
    NSString *fullPath = url.path;
    NSString *mimeType = nil;
    
    if (fullPath) {
        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
        if (typeId) {
            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
            if (!mimeType) {
                // special case for m4a
                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
                    mimeType = @"audio/mp4";
                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
                    mimeType = @"audio/wav";
                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
                    mimeType = @"text/css";
                }
            }
            CFRelease(typeId);
        }
    }
    return mimeType;
}

- (NSData *)decryptAES256WithKey:(NSString *)key iv:(NSString *)iv data:(NSString *)base64String {
    NSLog(@"inside decrypt");
    
    NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
    
    size_t bufferSize = [data length] + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesDecrypted = 0;
    
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    NSData *ivData = [iv dataUsingEncoding:NSUTF8StringEncoding];
    
    CCCryptorStatus status = CCCrypt(kCCDecrypt,
                                     kCCAlgorithmAES128,
                                     kCCOptionPKCS7Padding,
                                     keyData.bytes,
                                     kCCKeySizeAES256,
                                     ivData.bytes,
                                     data.bytes,
                                     data.length,
                                     buffer,
                                     bufferSize,
                                     &numBytesDecrypted);
    
    if (status == kCCSuccess) {
        return [NSData dataWithBytes:buffer length:numBytesDecrypted];
    }
    free(buffer);
    
    return nil;
}

- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
{
    NSString* mimeType = nil;
    
    if (fullPath) {
        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
        if (typeId) {
            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
            if (!mimeType) {
                // special case for m4a
                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
                    mimeType = @"audio/mp4";
                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
                    mimeType = @"audio/wav";
                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
                    mimeType = @"text/css";
                }
            }
            CFRelease(typeId);
        }
    }
    return mimeType;
}

- (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mimeType:(NSString*)mimeType
{
    NSLog(@"inside response");
    if (mimeType == nil) {
        mimeType = @"text/plain";
    }
    
    NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:[[self request] URL] statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:@{@"Content-Type" : mimeType}];
    
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    if (data != nil) {
        [[self client] URLProtocol:self didLoadData:data];
    }
    [[self client] URLProtocolDidFinishLoading:self];
}

+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
{
    NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
    return request;
}

@end

To explain this, the canInitWithRequest will receive the url "http://localhost:8080/index.html, then the checkCryptFilemethod will retrieve the extension example "html" and call thehasMatch` method.

Then in the startLoading method, I change this url to a valid file directory to be able to decrypt it, or you will get an error file not found. So, the good thing is that it decrypts the files, and in the logs I get:

2019-01-26 20:06:42.976473+0200 myApp[3848:75777] Finished load of: http://localhost:8080/index.html

But the problem is I keep getting a white screen with no data and nothing just a white screen.

So, it is decrypting and you can see in the log when you run it, but in the simulator (iPhone XR), I keep getting white screen after its loaded.

Note: Also in the config.xml, you need to add <allow-navigation href="http://localhost:8080/*" /> to be able to use localhost:8080.

Also im testing this without cordova-ionic-webview plugin, as crypto-file needs to work first, then we add ionic-webview and debug to check.

Summary:

Good Part:

Javascript part of cordova-crypto-file is done, files are getting decrypted. The app is being served on localhost:8080.

Bad Part:

After finishing loading, only white screen is appearing in the app, sometimes the design and the data appear in the screen though.

PeterHdd avatar Jan 26 '19 18:01 PeterHdd

@PeterHdd Thanks for trying. I am also looking it now.

an-rahulpandey avatar Jan 30 '19 08:01 an-rahulpandey

@PeterHdd The plugin works properly, the problem is with the decryption time. If you open the safari inspect window and press reload button you will see that it will work one in five times. The plugins JS files sometimes doesn't get decrypted properly which also gives error.

Also I have noticed that sometimes the files contents are overwritten by other files. See the Resources tab in inspect window and try to see some js files. For e.g device plugin js content will be also shown in splashscreen js file, main.js file content shown in vendor.js file, etc..

an-rahulpandey avatar Jan 30 '19 14:01 an-rahulpandey

@PeterHdd I am seeing same issue with decryption time, however my knowledge of objective-C is also null so I can't help you unfortunately. Hope you find a fix soon.

FawadNL avatar Feb 04 '19 11:02 FawadNL

According to my tests using cordova-plugin-ionic-webview, canInitWithRequest isn't even being instantiated. Something to do with CDVURLProtocol not working with WkWebView I suppose.

victor1342 avatar Apr 30 '19 02:04 victor1342

Hello, It is not working with iOS. The files are getting encrypted properly, but when I run the app via Xcode, the app gets stuck at splash screen. It shows encrypted content on a white screen. Ios version 12.2

parvageshahariar avatar May 05 '19 11:05 parvageshahariar

@PeterHdd where is the update? Still ios not working. Nothing there after January

parvageshahariar avatar May 07 '19 08:05 parvageshahariar

hello any updates ?????????????????????????

tarekMohamed8 avatar Jan 27 '21 20:01 tarekMohamed8

If there is an ios developer who is willing to fix this, please let me know (I can help pay for your time and efforts, God willing). Thank you.

givethanks1 avatar May 27 '21 19:05 givethanks1

@givethanks1 check the forks, maybe someone fixed it there

PeterHdd avatar May 27 '21 19:05 PeterHdd