diff --git a/README.md b/README.md index 89f33cb..3c5e6b9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# Cordova crypt file plugin -HTML source file is encrypted at build, and decrypted at run. -https://www.npmjs.com/package/cordova-plugin-crypt-file +# Cordova crypt file plugin NextGen +This is an extension to [tkyaji's cordova-plugin-crypt-file](https://github.com/tkyaji/cordova-plugin-crypt-file) implementation to encrypt HTML assets during build and to decrypt the required assets during runtime. + +The original implementation can also be found on https://www.npmjs.com/package/cordova-plugin-crypt-file. + +## Requires node-rsa +`npm install -g node-rsa` ## Add Plugin -`cordova plugin add cordova-plugin-crypt-file` +`cordova plugin add https://github.com/qhng/cordova-plugin-crypt-file` ## Encrypt `cordova build [ios / android]` @@ -43,9 +47,7 @@ Specify the target file as a regular expression. ## Supported platforms -* iOS * Android -* CrossWalk ## Before reporting your issue It would be very helpful if you show me your project (If you have GitHub repository, that URL would be nice). diff --git a/hooks/after_prepare.js b/hooks/after_prepare.js index f1dc3fa..d5cf208 100644 --- a/hooks/after_prepare.js +++ b/hooks/after_prepare.js @@ -8,15 +8,29 @@ module.exports = function(context) { 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; + ConfigParser = context.requireCordovaModule('cordova-common').ConfigParser, + NodeRSA = context.requireCordovaModule('node-rsa'); var deferral = new Q.defer(); var projectRoot = cordova_util.cdProjectRoot(); + var keypair = new NodeRSA(); + keypair.generateKeyPair(1024); var key = crypto.randomBytes(24).toString('base64'); var iv = crypto.randomBytes(12).toString('base64'); - console.log('key=' + key + ', iv=' + iv) + var publicKey = keypair.exportKey("pkcs8-public"); + console.log(publicKey); + console.log(keypair.exportKey("pkcs8-private")); + publicKey = publicKey.replace("-----BEGIN PUBLIC KEY-----", ""); + publicKey = publicKey.replace("-----END PUBLIC KEY-----", ""); + var publicKeyHexa = new Buffer(publicKey).toString("hex"); + publicKeyHexa = publicKeyHexa.replace(/0a/g, ""); + publicKey = new Buffer(publicKeyHexa, "hex").toString("ascii"); + var encryptedKey = keypair.encryptPrivate(key, "base64"); + var encryptedIv = keypair.encryptPrivate(iv, "base64"); + console.log('key(P)=' + key + ', iv(P)=' + iv) + console.log('key(E)=' + encryptedKey + ', iv(E)=' + encryptedIv) var targetFiles = loadCryptFileTargets(); @@ -38,25 +52,9 @@ module.exports = function(context) { console.log('encrypt: ' + file); }); - if (platform == 'ios') { - var pluginDir; - try { - var ios_parser = context.requireCordovaModule('cordova-lib/src/cordova/metadata/ios_parser'), - iosParser = new ios_parser(platformPath); - pluginDir = path.join(iosParser.cordovaproj, 'Plugins', context.opts.plugin.id); - } catch (err) { - var xcodeproj_dir = fs.readdirSync(platformPath).filter(function(e) { return e.match(/\.xcodeproj$/i); })[0], - xcodeproj = path.join(platformPath, xcodeproj_dir), - originalName = xcodeproj.substring(xcodeproj.lastIndexOf(path.sep)+1, xcodeproj.indexOf('.xcodeproj')), - cordovaproj = path.join(platformPath, originalName); - - pluginDir = path.join(cordovaproj, 'Plugins', context.opts.plugin.id); - } - replaceCryptKey_ios(pluginDir, key, iv); - - } else if (platform == 'android') { + if (platform == 'android') { var pluginDir = path.join(platformPath, 'src'); - replaceCryptKey_android(pluginDir, key, iv); + replaceCryptKey_android(pluginDir, encryptedKey, encryptedIv, publicKey); var cfg = new ConfigParser(platformInfo.projectConfig.path); cfg.doc.getroot().getchildren().filter(function(child, idx, arr) { @@ -135,32 +133,16 @@ module.exports = function(context) { return encrypted; } - function replaceCryptKey_ios(pluginDir, key, iv) { - var sourceFile = path.join(pluginDir, 'CDVCryptURLProtocol.m'); - var content = fs.readFileSync(sourceFile, 'utf-8'); - - var includeArrStr = targetFiles.include.map(function(pattern) { return '@"' + pattern.replace('\\', '\\\\') + '"'; }).join(', '); - var excludeArrStr = targetFiles.exclude.map(function(pattern) { return '@"' + pattern.replace('\\', '\\\\') + '"'; }).join(', '); - - content = content.replace(/kCryptKey = @".*";/, 'kCryptKey = @"' + key + '";') - .replace(/kCryptIv = @".*";/, 'kCryptIv = @"' + iv + '";') - .replace(/kIncludeFiles\[\] = {.*};/, 'kIncludeFiles\[\] = { ' + includeArrStr + ' };') - .replace(/kExcludeFiles\[\] = {.*};/, 'kExcludeFiles\[\] = { ' + excludeArrStr + ' };') - .replace(/kIncludeFileLength = [0-9]+;/, 'kIncludeFileLength = ' + targetFiles.include.length + ';') - .replace(/kExcludeFileLength = [0-9]+;/, 'kExcludeFileLength = ' + targetFiles.exclude.length + ';'); - - fs.writeFileSync(sourceFile, content, 'utf-8'); - } - - function replaceCryptKey_android(pluginDir, key, iv) { - var sourceFile = path.join(pluginDir, 'com/tkyaji/cordova/DecryptResource.java'); + function replaceCryptKey_android(pluginDir, encryptedKey, encryptedIv, publicPem) { + var sourceFile = path.join(pluginDir, 'com/qhng/cordova/DecryptResourceNG.java'); var content = fs.readFileSync(sourceFile, 'utf-8'); var includeArrStr = targetFiles.include.map(function(pattern) { return '"' + pattern.replace('\\', '\\\\') + '"'; }).join(', '); var excludeArrStr = targetFiles.exclude.map(function(pattern) { return '"' + pattern.replace('\\', '\\\\') + '"'; }).join(', '); - content = content.replace(/CRYPT_KEY = ".*";/, 'CRYPT_KEY = "' + key + '";') - .replace(/CRYPT_IV = ".*";/, 'CRYPT_IV = "' + iv + '";') + content = content.replace(/_CRYPT_KEY = ".*";/, '_CRYPT_KEY = "' + encryptedKey + '";') + .replace(/_CRYPT_IV = ".*";/, '_CRYPT_IV = "' + encryptedIv + '";') + .replace(/PUBLIC_PEM = ".*";/, 'PUBLIC_PEM = "' + publicPem + '";') .replace(/INCLUDE_FILES = new String\[\] {.*};/, 'INCLUDE_FILES = new String[] { ' + includeArrStr + ' };') .replace(/EXCLUDE_FILES = new String\[\] {.*};/, 'EXCLUDE_FILES = new String[] { ' + excludeArrStr + ' };'); diff --git a/package.json b/package.json index 6175289..00eb67a 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,18 @@ { - "name": "cordova-plugin-crypt-file", - "version": "1.3.3", + "name": "cordova-plugin-crypt-file-ng", + "version": "0.1.0", "description": "This plugin to encrypt/decrypt the source files.", "repository": { "type": "git", - "url": "https://github.com/tkyaji/cordova-plugin-crypt-file.git" + "url": "https://github.com/qhng/cordova-plugin-crypt-file.git" }, "keywords": [ "ecosystem:cordova", - "cordova-android", - "cordova-ios" + "cordova-android" ], - "author": "tkyaji", + "dependencies": { + "node-rsa": "0.4.2" + }, + "author": "tkyaji, qhng", "license": "Apache version 2.0" } diff --git a/plugin.xml b/plugin.xml index cfc0dbf..990e985 100755 --- a/plugin.xml +++ b/plugin.xml @@ -1,36 +1,22 @@ + id="cordova-plugin-crypt-file-ng" + version="0.1.0"> CordovaCrypt Plugin Description - @tkyaji + @tkyaji, @qhng Apache 2.0 License - - - - - - - - - - - - - - - - + + - + diff --git a/src/android/com/tkyaji/cordova/DecryptResource.java b/src/android/com/qhng/cordova/DecryptResourceNG.java similarity index 71% rename from src/android/com/tkyaji/cordova/DecryptResource.java rename to src/android/com/qhng/cordova/DecryptResourceNG.java index 71dbe6d..a0b1940 100644 --- a/src/android/com/tkyaji/cordova/DecryptResource.java +++ b/src/android/com/qhng/cordova/DecryptResourceNG.java @@ -1,4 +1,4 @@ -package com.tkyaji.cordova; +package com.qhng.cordova; import android.net.Uri; import android.util.Base64; @@ -19,15 +19,30 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; -public class DecryptResource extends CordovaPlugin { - private static final String TAG = "DecryptResource"; +public class DecryptResourceNG extends CordovaPlugin { - private static final String CRYPT_KEY = ""; - private static final String CRYPT_IV = ""; + private static final String TAG = "DecryptResourceNG"; + + private static final String PUBLIC_PEM = ""; + private static final String _CRYPT_KEY = ""; + private static final String _CRYPT_IV = ""; private static final String[] INCLUDE_FILES = new String[] { }; private static final String[] EXCLUDE_FILES = new String[] { }; + private final String CRYPT_KEY; + private final String CRYPT_IV; + + public DecryptResourceNG() throws Exception { + PublicKey pubKey = PublicKeyReader.get(PUBLIC_PEM); + Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + rsa.init(Cipher.DECRYPT_MODE, pubKey); + CRYPT_KEY = new String(rsa.doFinal(Base64.decode(_CRYPT_KEY, Base64.DEFAULT))); + CRYPT_IV = new String(rsa.doFinal(Base64.decode(_CRYPT_IV, Base64.DEFAULT))); + } @Override public Uri remapUri(Uri uri) { @@ -98,3 +113,12 @@ private boolean hasMatch(String text, String[] regexArr) { return false; } } + +class PublicKeyReader { + public static PublicKey get(String publicPemStr) throws Exception { + byte[] keyBytes = Base64.decode(publicPemStr, Base64.DEFAULT); + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(spec); + } +} diff --git a/src/ios/CDVCrypt.h b/src/ios/CDVCrypt.h deleted file mode 100644 index 78124f6..0000000 --- a/src/ios/CDVCrypt.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// CDVCrypt.h -// CordovaLib -// -// Created by tkyaji on 2015/07/17. -// -// - -#import - -@interface CDVCrypt : CDVPlugin - -- (void)pluginInitialize; - -@end diff --git a/src/ios/CDVCrypt.m b/src/ios/CDVCrypt.m deleted file mode 100644 index 93c3b12..0000000 --- a/src/ios/CDVCrypt.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// CDVCrypt.m -// CordovaLib -// -// Created by tkyaji on 2015/07/17. -// -// - -#import "CDVCrypt.h" -#import "CDVCryptURLProtocol.h" - -@implementation CDVCrypt - -- (void)pluginInitialize -{ - [NSURLProtocol registerClass:[CDVCryptURLProtocol class]]; -} - -@end diff --git a/src/ios/CDVCryptURLProtocol.h b/src/ios/CDVCryptURLProtocol.h deleted file mode 100644 index 9d32664..0000000 --- a/src/ios/CDVCryptURLProtocol.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// CDVCryptURLProtocol.h -// CordovaLib -// -// Created by tkyaji on 2015/07/15. -// -// - -#import - -@interface CDVCryptURLProtocol : CDVURLProtocol - -@end diff --git a/src/ios/CDVCryptURLProtocol.m b/src/ios/CDVCryptURLProtocol.m deleted file mode 100644 index 726abcf..0000000 --- a/src/ios/CDVCryptURLProtocol.m +++ /dev/null @@ -1,192 +0,0 @@ -// -// CDVCryptURLProtocol.m -// CordovaLib -// -// Created by tkyaji on 2015/07/15. -// -// - -#import "CDVCryptURLProtocol.h" - -#import -#import -#import - - -static NSString* const kCryptKey = @""; -static NSString* const kCryptIv = @""; - -static int const kIncludeFileLength = 0; -static int const kExcludeFileLength = 0; -static NSString* const kIncludeFiles[] = { }; -static NSString* const kExcludeFiles[] = { }; - - -@implementation CDVCryptURLProtocol - -+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest -{ - if ([self checkCryptFile:theRequest.URL]) { - return YES; - } - - return [super canInitWithRequest:theRequest]; -} - -- (void)startLoading -{ - NSURL* url = self.request.URL; - - 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) { - NSData* data = [self decryptAES256WithKey:kCryptKey iv:kCryptIv data:content]; - [self sendResponseWithResponseCode:200 data:data mimeType:mimeType]; - } - } - - [super startLoading]; -} - -+ (BOOL)checkCryptFile:(NSURL *)url { - if (![url.scheme isEqual: @"file"]) { - return NO; - } - - NSLog(@"%@", url.path); - - NSString *wwwPath = [[NSBundle mainBundle].resourcePath stringByAppendingString:@"/www/"]; - NSString *checkPath = [url.path stringByReplacingOccurrencesOfString:wwwPath withString:@""]; - - 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 { - - 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 -{ - 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]; -} - - -@end