PeterHdd / cordova-plugin-crypto-file

Encrypts source files, works with cordova-plugin-ionic-webview
Apache License 2.0
30 stars 28 forks source link

iOS Support #2

Open FawadNL opened 5 years ago

FawadNL commented 5 years ago

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.

PeterHdd commented 5 years ago

Hello,

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

FawadNL commented 5 years ago

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?

PeterHdd commented 5 years ago

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

FawadNL commented 5 years ago

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

an-rahulpandey commented 5 years ago

@PeterHdd I am also facing the same issue.

PeterHdd commented 5 years ago

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

an-rahulpandey commented 5 years ago

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

PeterHdd commented 5 years ago

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

PeterHdd commented 5 years ago

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 thecheckCryptFilemethod 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.

an-rahulpandey commented 5 years ago

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

an-rahulpandey commented 5 years ago

@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..

FawadNL commented 5 years ago

@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.

victor1342 commented 5 years ago

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.

parvageshahariar commented 5 years ago

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 commented 5 years ago

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

tarekMohamed8 commented 3 years ago

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

givethanks1 commented 3 years ago

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.

PeterHdd commented 3 years ago

@givethanks1 check the forks, maybe someone fixed it there