Skip to content
13 changes: 5 additions & 8 deletions hooks/after_prepare.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
module.exports = function(context) {

var path = context.requireCordovaModule('path'),
fs = context.requireCordovaModule('fs'),
crypto = context.requireCordovaModule('crypto'),
Q = context.requireCordovaModule('q'),
var path = require('path'),
fs = require('fs'),
crypto = require('crypto'),
Q = require('q'),
cordova_util = context.requireCordovaModule('cordova-lib/src/cordova/util'),
platforms = context.requireCordovaModule('cordova-lib/src/platforms/platforms'),
Parser = context.requireCordovaModule('cordova-lib/src/cordova/metadata/parser'),
ParserHelper = context.requireCordovaModule('cordova-lib/src/cordova/metadata/parserhelper/ParserHelper'),
ConfigParser = context.requireCordovaModule('cordova-common').ConfigParser;

var deferral = new Q.defer();
Expand Down Expand Up @@ -166,4 +163,4 @@ module.exports = function(context) {

fs.writeFileSync(sourceFile, content, 'utf-8');
}
}
}
11 changes: 10 additions & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova-plugin-crypt-file"
version="1.3.3">
version="1.4.0">

<name>CordovaCrypt</name>
<description>Plugin Description</description>
<author>@tkyaji</author>
<license>Apache 2.0 License</license>

<engines>
<engine name="cordova" version=">=9.0.0"/>
<engine name="cordova-android" version=">=9.0.0" />
<engine name="cordova-ios" version=">=6.1.0" />
</engines>

<platform name="ios">
<config-file target="config.xml" parent="/*">
Expand All @@ -31,6 +37,9 @@
</config-file>

<source-file src="src/android/com/tkyaji/cordova/DecryptResource.java" target-dir="src/com/tkyaji/cordova" />
<source-file src="src/android/com/tkyaji/cordova/DecryptCordovaPathHandler.java" target-dir="src/com/tkyaji/cordova" />

<framework src="androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}" />
</platform>

<cryptfiles>
Expand Down
126 changes: 126 additions & 0 deletions src/android/com/tkyaji/cordova/DecryptCordovaPathHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.tkyaji.cordova;

import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.Base64;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.WebResourceResponse;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.WebViewAssetLoader;

import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.LOG;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Pattern;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class DecryptCordovaPathHandler implements WebViewAssetLoader.PathHandler {
private static final String TAG = "DecryptResource";

public CordovaWebView webView;
public CordovaInterface cordova;

private final String[] INCLUDE_FILES;
private final String[] EXCLUDE_FILES;
private final String CRYPT_KEY;
private final String CRYPT_IV;

public DecryptCordovaPathHandler(CordovaWebView webView,
CordovaInterface cordova,
String[] INCLUDE_FILES, String[] EXCLUDE_FILES,
String CRYPT_KEY, String CRYPT_IV) {
this.webView = webView;
this.cordova = cordova;
this.INCLUDE_FILES = INCLUDE_FILES;
this.EXCLUDE_FILES = EXCLUDE_FILES;
this.CRYPT_KEY = CRYPT_KEY;
this.CRYPT_IV = CRYPT_IV;
}

private boolean isCryptFiles(String uri) {
String checkPath = uri.replace("file:///android_asset/www/", "");
if (!this.hasMatch(checkPath, INCLUDE_FILES)) {
return false;
}
if (this.hasMatch(checkPath, EXCLUDE_FILES)) {
return false;
}
return true;
}

private boolean hasMatch(String text, String[] regexArr) {
for (String regex : regexArr) {
if (Pattern.compile(regex).matcher(text).find()) {
return true;
}
}
return false;
}

@Nullable
@Override
public WebResourceResponse handle(@NonNull String path) {
if (path.startsWith("+++/")) {
String newPath = "www/" + path.replace("+++/", "").split("\\?")[0];

try {
CordovaResourceApi resourceApi = this.webView.getResourceApi();
String mimeType = resourceApi.getMimeType(Uri.parse("file://" + newPath));
InputStream is = webView.getContext().getAssets().open(newPath, AssetManager.ACCESS_STREAMING);

if (!isCryptFiles(path)) {
return new WebResourceResponse(mimeType, null, is);
}

BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder strb = new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
strb.append(line);
}
br.close();

byte[] bytes = Base64.decode(strb.toString(), Base64.DEFAULT);

LOG.d(TAG, "decrypt: " + newPath);
ByteArrayInputStream byteInputStream = null;
try {
SecretKey skey = new SecretKeySpec(CRYPT_KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(CRYPT_IV.getBytes("UTF-8")));

ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(cipher.doFinal(bytes));
byteInputStream = new ByteArrayInputStream(bos.toByteArray());
} catch (Exception ex) {
LOG.e(TAG, ex.getMessage());
}

return new WebResourceResponse(mimeType, null, byteInputStream);
}
catch (IOException e) {
Log.e(TAG, e.getLocalizedMessage());
}
} else {
return null;
}

return null;
}
}
11 changes: 10 additions & 1 deletion src/android/com/tkyaji/cordova/DecryptResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.util.Base64;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaPluginPathHandler;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;

Expand All @@ -29,6 +30,14 @@ public class DecryptResource extends CordovaPlugin {
private static final String[] INCLUDE_FILES = new String[] { };
private static final String[] EXCLUDE_FILES = new String[] { };

@Override
public CordovaPluginPathHandler getPathHandler() {
return new CordovaPluginPathHandler(
new DecryptCordovaPathHandler(this.webView, this.cordova,
DecryptResource.INCLUDE_FILES, DecryptResource.EXCLUDE_FILES,
DecryptResource.CRYPT_KEY, DecryptResource.CRYPT_IV));
}

@Override
public Uri remapUri(Uri uri) {
if (uri.toString().indexOf("/+++/") > -1) {
Expand Down Expand Up @@ -79,7 +88,7 @@ public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IO
}

private boolean isCryptFiles(String uri) {
String checkPath = uri.replace("file:///android_asset/www/", "");
String checkPath = uri.replace("file:///android_asset/www/", "").replace("www/", "");
if (!this.hasMatch(checkPath, INCLUDE_FILES)) {
return false;
}
Expand Down
9 changes: 9 additions & 0 deletions src/ios/CDVCrypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@
//

#import <Cordova/CDVPlugin.h>
#import <Cordova/CDVViewController.h>
#import <WebKit/WebKit.h>
#import "CDVCryptURLProtocol.h"

@interface CDVCrypt : CDVPlugin

- (void)pluginInitialize;

#ifdef HASCDVUrlProtocol
#else
@property (nonatomic) NSMutableArray* stoppedTasks;
@property (nonatomic) CDVCryptURLProtocol* protocol;
#endif

@end
59 changes: 58 additions & 1 deletion src/ios/CDVCrypt.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,70 @@
//

#import "CDVCrypt.h"
#import "CDVCryptURLProtocol.h"

@implementation CDVCrypt

- (void)pluginInitialize
{
#ifdef HASCDVUrlProtocol
[NSURLProtocol registerClass:[CDVCryptURLProtocol class]];
#else
self.stoppedTasks = [[NSMutableArray alloc] init];
self.protocol = [[CDVCryptURLProtocol alloc] init];
#endif
}

#ifdef HASCDVUrlProtocol
#else
- (BOOL)overrideSchemeTask: (id <WKURLSchemeTask>)urlSchemeTask
{
if([[self.protocol class] canInitWithRequest:urlSchemeTask.request]) {
NSURL * url = urlSchemeTask.request.URL;

if([[self.protocol class] checkCryptFile:url]) {
NSError* error;

NSString * startPath = [[NSBundle mainBundle] pathForResource:((CDVViewController *) self.viewController).wwwFolderName ofType: nil];
NSString * filePath = [startPath stringByAppendingString:url.path];

NSString* content = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (!error) {
NSData* data = [self.protocol decryptContent:content];

NSString * length = [NSString stringWithFormat:@"%lu", (unsigned long) [data length]];
NSString * mimeType = [self.protocol getMimeTypeFromPath:url.path];
NSDictionary * headersDict = [NSDictionary dictionaryWithObjectsAndKeys:length, @"Content-Length", mimeType, @"Content-Type", nil];

NSHTTPURLResponse * response = [[NSHTTPURLResponse alloc] initWithURL:[urlSchemeTask.request URL] statusCode:200 HTTPVersion:@"1.1" headerFields:headersDict];

// Do not use urlSchemeTask if it has been closed in stopURLSchemeTask. Otherwise the app will crash.
@try {
if(self.stoppedTasks == nil || ![self.stoppedTasks containsObject:urlSchemeTask]) {
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data];
[urlSchemeTask didFinish];
} else {
NSLog(@"CDVCrypt Task stopped %@", startPath);
}
} @catch (NSException *exception) {
NSLog(@"CDVCrypt send response exception: %@", exception.debugDescription);
} @finally {
// Cleanup
[self.stoppedTasks removeObject:urlSchemeTask];
}
}

return YES;
}
}

return NO;
}

- (void) stopSchemeTask: (id <WKURLSchemeTask>)urlSchemeTask {
NSLog(@"Stop CDVCrypt %@", urlSchemeTask.debugDescription);
[self.stoppedTasks addObject:urlSchemeTask];
}
#endif

@end
17 changes: 17 additions & 0 deletions src/ios/CDVCryptURLProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,25 @@
//
//

#if __has_include(<Cordova/CDVURLProtocol.h>)
#import <Cordova/CDVURLProtocol.h>
#define HASCDVUrlProtocol
#endif

#ifdef HASCDVUrlProtocol
@interface CDVCryptURLProtocol : CDVURLProtocol

@end
#else
@interface CDVCryptURLProtocol : NSObject

+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest;
+ (BOOL)checkCryptFile:(NSURL *)url;

- (NSData*)decryptContent:(NSString *)content;
- (NSString*)getMimeType:(NSURL *)url;
- (NSData *)decryptAES256WithKey:(NSString *)key iv:(NSString *)iv data:(NSString *)base64String;
- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;

@end
#endif
27 changes: 19 additions & 8 deletions src/ios/CDVCryptURLProtocol.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
#import <CommonCrypto/CommonDigest.h>


static NSString* const kCryptKey = @"";
static NSString* const kCryptIv = @"";
static NSString* const kCryptKey = @"X7fn1HZYyiCgtoY/fzTlcnS7DM00Isbo";
static NSString* const kCryptIv = @"YiLfJKgdr+DWV+9r";

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


@implementation CDVCryptURLProtocol
Expand All @@ -29,10 +29,15 @@ + (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
if ([self checkCryptFile:theRequest.URL]) {
return YES;
}


#ifdef HASCDVUrlProtocol
return [super canInitWithRequest:theRequest];
#else
return NO;
#endif
}

#ifdef HASCDVUrlProtocol
- (void)startLoading
{
NSURL* url = self.request.URL;
Expand All @@ -50,9 +55,14 @@ - (void)startLoading

[super startLoading];
}
#else
- (NSData*)decryptContent:(NSString *)content {
return [self decryptAES256WithKey:kCryptKey iv:kCryptIv data:content];
}
#endif

+ (BOOL)checkCryptFile:(NSURL *)url {
if (![url.scheme isEqual: @"file"]) {
if (![url.scheme isEqual: @"app"]) {
return NO;
}

Expand Down Expand Up @@ -173,6 +183,7 @@ - (NSString*)getMimeTypeFromPath:(NSString*)fullPath
return mimeType;
}

#ifdef HASCDVUrlProtocol
- (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mimeType:(NSString*)mimeType
{
if (mimeType == nil) {
Expand All @@ -187,6 +198,6 @@ - (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mi
}
[[self client] URLProtocolDidFinishLoading:self];
}

#endif

@end