Refactor code signing (#313)
* Refactor code-signing JS and android * Small fixes and refactoring for JS and android Add ignore list parameter for copying packages files Rename unzipDir to deployDir for consistency Fix wrong parrameters passing to verify function Fix native android parsePublicKey method Remove redundant line in native code * Minor fixes for android and ios Add MACOSX directory to ignore for hashing Update ios native API * Add additional signature clearing * Bump JWT version dependency
This commit is contained in:
Родитель
799c499dcb
Коммит
645c899081
|
@ -95,7 +95,13 @@ var FileUtil = (function () {
|
|||
FileUtil.dataDirectoryExists = function (path, callback) {
|
||||
FileUtil.directoryExists(cordova.file.dataDirectory, path, callback);
|
||||
};
|
||||
FileUtil.copyDirectoryEntriesTo = function (sourceDir, destinationDir, callback) {
|
||||
FileUtil.copyDirectoryEntriesTo = function (sourceDir, destinationDir, ignoreList, callback) {
|
||||
if (ignoreList.indexOf(".DS_Store") === -1) {
|
||||
ignoreList.push(".DS_Store");
|
||||
}
|
||||
if (ignoreList.indexOf("__MACOSX") === -1) {
|
||||
ignoreList.push("__MACOSX");
|
||||
}
|
||||
var fail = function (error) {
|
||||
callback(FileUtil.fileErrorToError(error), null);
|
||||
};
|
||||
|
@ -104,7 +110,7 @@ var FileUtil = (function () {
|
|||
var copyOne = function () {
|
||||
if (i < entries.length) {
|
||||
var nextEntry = entries[i++];
|
||||
if (nextEntry.name === ".DS_Store" || nextEntry.name === "__MACOSX") {
|
||||
if (ignoreList.indexOf(nextEntry.name) > 0) {
|
||||
copyOne();
|
||||
}
|
||||
else {
|
||||
|
@ -113,7 +119,7 @@ var FileUtil = (function () {
|
|||
callback(new Error("Error during entry replacement. Error code: " + fileError.code), null);
|
||||
};
|
||||
if (destinationEntry.isDirectory) {
|
||||
FileUtil.copyDirectoryEntriesTo(nextEntry, destinationEntry, function (error) {
|
||||
FileUtil.copyDirectoryEntriesTo(nextEntry, destinationEntry, ignoreList, function (error) {
|
||||
if (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
/********************************************************************************************
|
||||
THIS FILE HAS BEEN COMPILED FROM TYPESCRIPT SOURCES.
|
||||
PLEASE DO NOT MODIFY THIS FILE DIRECTLY AS YOU WILL LOSE YOUR CHANGES WHEN RECOMPILING.
|
||||
INSTEAD, EDIT THE TYPESCRIPT SOURCES UNDER THE WWW FOLDER, AND THEN RUN GULP.
|
||||
FOR MORE INFORMATION, PLEASE SEE CONTRIBUTING.md.
|
||||
*********************************************************************************************/
|
||||
/********************************************************************************************
|
||||
THIS FILE HAS BEEN COMPILED FROM TYPESCRIPT SOURCES.
|
||||
PLEASE DO NOT MODIFY THIS FILE DIRECTLY AS YOU WILL LOSE YOUR CHANGES WHEN RECOMPILING.
|
||||
INSTEAD, EDIT THE TYPESCRIPT SOURCES UNDER THE WWW FOLDER, AND THEN RUN GULP.
|
||||
FOR MORE INFORMATION, PLEASE SEE CONTRIBUTING.md.
|
||||
*********************************************************************************************/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
@ -43,13 +43,6 @@ var LocalPackage = (function (_super) {
|
|||
Sdk.reportStatusDeploy(_this, AcquisitionStatus.DeploymentFailed, _this.deploymentKey);
|
||||
};
|
||||
var newPackageLocation = LocalPackage.VersionsDir + "/" + this.packageHash;
|
||||
var signatureVerified = function (deployDir) {
|
||||
_this.localPath = deployDir.fullPath;
|
||||
_this.finishInstall(deployDir, installOptions, installSuccess, installError);
|
||||
};
|
||||
var donePackageFileCopy = function (deployDir) {
|
||||
_this.verifyPackage(deployDir, installError, CodePushUtil.getNodeStyleCallbackFor(signatureVerified, installError));
|
||||
};
|
||||
var newPackageUnzipped = function (unzipError) {
|
||||
if (unzipError) {
|
||||
installError && installError(new Error("Could not unzip package" + CodePushUtil.getErrorMessage(unzipError)));
|
||||
|
@ -58,6 +51,15 @@ var LocalPackage = (function (_super) {
|
|||
LocalPackage.handleDeployment(newPackageLocation, CodePushUtil.getNodeStyleCallbackFor(donePackageFileCopy, installError));
|
||||
}
|
||||
};
|
||||
var donePackageFileCopy = function (deploymentResult) {
|
||||
_this.verifyPackage(deploymentResult, installError, function () {
|
||||
packageVerified(deploymentResult.deployDir);
|
||||
});
|
||||
};
|
||||
var packageVerified = function (deployDir) {
|
||||
_this.localPath = deployDir.fullPath;
|
||||
_this.finishInstall(deployDir, installOptions, installSuccess, installError);
|
||||
};
|
||||
FileUtil.getDataDirectory(LocalPackage.DownloadUnzipDir, false, function (error, directoryEntry) {
|
||||
var unzipPackage = function () {
|
||||
FileUtil.getDataDirectory(LocalPackage.DownloadUnzipDir, true, function (innerError, unzipDir) {
|
||||
|
@ -84,44 +86,122 @@ var LocalPackage = (function (_super) {
|
|||
installError && installError(new Error("An error occured while installing the package. " + CodePushUtil.getErrorMessage(e)));
|
||||
}
|
||||
};
|
||||
LocalPackage.prototype.verifyPackage = function (unzipDir, installError, callback) {
|
||||
LocalPackage.prototype.verifyPackage = function (deploymentResult, installError, successCallback) {
|
||||
var _this = this;
|
||||
var packageHashSuccess = function (localHash) {
|
||||
CodePushUtil.logMessage("Expected hash: " + _this.packageHash + ", actual hash: " + localHash);
|
||||
FileUtil.readFile(cordova.file.dataDirectory, unzipDir.fullPath + '/www', '.codepushrelease', function (error, contents) {
|
||||
var verifySignatureSuccess = function (expectedHash) {
|
||||
if (localHash !== _this.packageHash) {
|
||||
installError(new Error("package hash verification failed"));
|
||||
return;
|
||||
var deployDir = deploymentResult.deployDir;
|
||||
var verificationFail = function (error) {
|
||||
installError && installError(error);
|
||||
};
|
||||
var verify = function (isSignatureVerificationEnabled, isSignatureAppearedInBundle, publicKey, signature) {
|
||||
if (isSignatureVerificationEnabled) {
|
||||
if (isSignatureAppearedInBundle) {
|
||||
_this.verifyHash(deployDir, _this.packageHash, verificationFail, function () {
|
||||
_this.verifySignature(deployDir, _this.packageHash, publicKey, signature, verificationFail, successCallback);
|
||||
});
|
||||
}
|
||||
else {
|
||||
var errorMessage = "Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
|
||||
"Possible reasons, why that might happen: \n" +
|
||||
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
|
||||
"2. You've been released CodePush bundle update without providing --privateKeyPath option.";
|
||||
installError && installError(new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isSignatureAppearedInBundle) {
|
||||
CodePushUtil.logMessage("Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
|
||||
"Please ensure that public key is properly configured within your application.");
|
||||
_this.verifyHash(deployDir, _this.packageHash, verificationFail, successCallback);
|
||||
}
|
||||
else {
|
||||
if (deploymentResult.isDiffUpdate) {
|
||||
_this.verifyHash(deployDir, _this.packageHash, verificationFail, successCallback);
|
||||
}
|
||||
if (!expectedHash) {
|
||||
CodePushUtil.logMessage("The update contents succeeded the data integrity check.");
|
||||
callback(null, unzipDir);
|
||||
if (contents != null) {
|
||||
CodePushUtil.logMessage("Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. \n" +
|
||||
"Please ensure that a public key is properly configured within your application.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (localHash === expectedHash) {
|
||||
CodePushUtil.logMessage("The update contents succeeded the code signing check.");
|
||||
callback(null, unzipDir);
|
||||
return;
|
||||
}
|
||||
installError(new Error("The update contents failed the code signing check."));
|
||||
};
|
||||
var verifySignatureFail = function (error) {
|
||||
installError && installError(new Error("The update contents failed the code signing check. " + error));
|
||||
};
|
||||
CodePushUtil.logMessage("Verifying signature for folder path: " + unzipDir.fullPath);
|
||||
cordova.exec(verifySignatureSuccess, verifySignatureFail, "CodePush", "verifySignature", [contents]);
|
||||
successCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (deploymentResult.isDiffUpdate) {
|
||||
CodePushUtil.logMessage("Applying diff update");
|
||||
}
|
||||
else {
|
||||
CodePushUtil.logMessage("Applying full update");
|
||||
}
|
||||
var isSignatureVerificationEnabled, isSignatureAppearedInBundle;
|
||||
var publicKey;
|
||||
this.getPublicKey(function (error, publicKeyResult) {
|
||||
if (error) {
|
||||
installError && installError(new Error("Error reading public key. " + error));
|
||||
return;
|
||||
}
|
||||
publicKey = publicKeyResult;
|
||||
isSignatureVerificationEnabled = (publicKey !== null);
|
||||
_this.getSignatureFromUpdate(deploymentResult.deployDir, function (error, signature) {
|
||||
if (error) {
|
||||
installError && installError(new Error("Error reading signature from update. " + error));
|
||||
return;
|
||||
}
|
||||
isSignatureAppearedInBundle = (signature !== null);
|
||||
verify(isSignatureVerificationEnabled, isSignatureAppearedInBundle, publicKey, signature);
|
||||
});
|
||||
});
|
||||
};
|
||||
LocalPackage.prototype.getPublicKey = function (callback) {
|
||||
var success = function (publicKey) {
|
||||
callback(null, publicKey);
|
||||
};
|
||||
var fail = function (error) {
|
||||
callback(error, null);
|
||||
};
|
||||
cordova.exec(success, fail, "CodePush", "getPublicKey", []);
|
||||
};
|
||||
LocalPackage.prototype.getSignatureFromUpdate = function (deployDir, callback) {
|
||||
var rootUri = cordova.file.dataDirectory;
|
||||
var path = deployDir.fullPath + '/www';
|
||||
var fileName = '.codepushrelease';
|
||||
FileUtil.fileExists(rootUri, path, fileName, function (error, result) {
|
||||
if (!result) {
|
||||
callback(null, null);
|
||||
return;
|
||||
}
|
||||
FileUtil.readFile(rootUri, path, fileName, function (error, signature) {
|
||||
if (error) {
|
||||
callback(error, null);
|
||||
return;
|
||||
}
|
||||
callback(null, signature);
|
||||
});
|
||||
});
|
||||
};
|
||||
LocalPackage.prototype.verifyHash = function (deployDir, newUpdateHash, errorCallback, successCallback) {
|
||||
var packageHashSuccess = function (computedHash) {
|
||||
if (computedHash !== newUpdateHash) {
|
||||
errorCallback(new Error("The update contents failed the data integrity check."));
|
||||
return;
|
||||
}
|
||||
CodePushUtil.logMessage("The update contents succeeded the data integrity check.");
|
||||
successCallback();
|
||||
};
|
||||
var packageHashFail = function (error) {
|
||||
installError && installError(new Error("unable to compute hash for package: " + error));
|
||||
errorCallback(new Error("Unable to compute hash for package: " + error));
|
||||
};
|
||||
CodePushUtil.logMessage("Verifying hash for folder path: " + unzipDir.fullPath);
|
||||
cordova.exec(packageHashSuccess, packageHashFail, "CodePush", "getPackageHash", [unzipDir.fullPath]);
|
||||
CodePushUtil.logMessage("Verifying hash for folder path: " + deployDir.fullPath);
|
||||
cordova.exec(packageHashSuccess, packageHashFail, "CodePush", "getPackageHash", [deployDir.fullPath]);
|
||||
};
|
||||
LocalPackage.prototype.verifySignature = function (deployDir, newUpdateHash, publicKey, signature, errorCallback, successCallback) {
|
||||
var decodeSignatureSuccess = function (contentHash) {
|
||||
if (contentHash !== newUpdateHash) {
|
||||
errorCallback(new Error("The update contents failed the code signing check."));
|
||||
return;
|
||||
}
|
||||
CodePushUtil.logMessage("The update contents succeeded the code signing check.");
|
||||
successCallback();
|
||||
};
|
||||
var decodeSignatureFail = function (error) {
|
||||
errorCallback(new Error("Unable to verify signature for package: " + error));
|
||||
};
|
||||
CodePushUtil.logMessage("Verifying signature for folder path: " + deployDir.fullPath);
|
||||
cordova.exec(decodeSignatureSuccess, decodeSignatureFail, "CodePush", "decodeSignature", [publicKey, signature]);
|
||||
};
|
||||
LocalPackage.prototype.finishInstall = function (deployDir, installOptions, installSuccess, installError) {
|
||||
var _this = this;
|
||||
|
@ -177,7 +257,7 @@ var LocalPackage = (function (_super) {
|
|||
}
|
||||
else {
|
||||
LocalPackage.handleCleanDeployment(newPackageLocation, function (error) {
|
||||
deployCallback(error, deployDir);
|
||||
deployCallback(error, { deployDir: deployDir, isDiffUpdate: false });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -214,19 +294,19 @@ var LocalPackage = (function (_super) {
|
|||
cleanDeployCallback(new Error("Could not copy new package."), null);
|
||||
}
|
||||
else {
|
||||
FileUtil.copyDirectoryEntriesTo(unzipDir, deployDir, function (copyError) {
|
||||
FileUtil.copyDirectoryEntriesTo(unzipDir, deployDir, [], function (copyError) {
|
||||
if (copyError) {
|
||||
cleanDeployCallback(copyError, null);
|
||||
}
|
||||
else {
|
||||
cleanDeployCallback(null, deployDir);
|
||||
cleanDeployCallback(null, { deployDir: deployDir, isDiffUpdate: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
LocalPackage.copyCurrentPackage = function (newPackageLocation, copyCallback) {
|
||||
LocalPackage.copyCurrentPackage = function (newPackageLocation, ignoreList, copyCallback) {
|
||||
var handleError = function (e) {
|
||||
copyCallback && copyCallback(e, null);
|
||||
};
|
||||
|
@ -249,7 +329,7 @@ var LocalPackage = (function (_super) {
|
|||
}
|
||||
else {
|
||||
var success = function (currentPackageDirectory) {
|
||||
FileUtil.copyDirectoryEntriesTo(currentPackageDirectory, deployDir, copyCallback);
|
||||
FileUtil.copyDirectoryEntriesTo(currentPackageDirectory, deployDir, ignoreList, copyCallback);
|
||||
};
|
||||
var fail = function (fileSystemError) {
|
||||
copyCallback && copyCallback(FileUtil.fileErrorToError(fileSystemError), null);
|
||||
|
@ -270,7 +350,7 @@ var LocalPackage = (function (_super) {
|
|||
var handleError = function (e) {
|
||||
diffCallback(e, null);
|
||||
};
|
||||
LocalPackage.copyCurrentPackage(newPackageLocation, function (currentPackageError) {
|
||||
LocalPackage.copyCurrentPackage(newPackageLocation, [".codepushrelease"], function (currentPackageError) {
|
||||
LocalPackage.handleCleanDeployment(newPackageLocation, function (cleanDeployError) {
|
||||
FileUtil.readFileEntry(diffManifest, function (error, content) {
|
||||
if (error || currentPackageError || cleanDeployError) {
|
||||
|
@ -284,7 +364,7 @@ var LocalPackage = (function (_super) {
|
|||
handleError(new Error("Cannot clean up deleted manifest files."));
|
||||
}
|
||||
else {
|
||||
diffCallback(null, deployDir);
|
||||
diffCallback(null, { deployDir: deployDir, isDiffUpdate: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
</platform>
|
||||
|
||||
<platform name="ios">
|
||||
<framework src="JWT" type="podspec" spec="3.0.0-beta.4" />
|
||||
<framework src="JWT" type="podspec" spec="3.0.0-beta.6" />
|
||||
<source-file src="src/ios/CDVWKWebViewEngine+CodePush.m" />
|
||||
<header-file src="src/ios/CodePush.h" />
|
||||
<source-file src="src/ios/CodePush.m" />
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.json.JSONException;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.MalformedURLException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -37,6 +38,7 @@ public class CodePush extends CordovaPlugin {
|
|||
private static final String PUBLIC_KEY_PREFERENCE = "codepushpublickey";
|
||||
private static final String SERVER_URL_PREFERENCE = "codepushserverurl";
|
||||
private static final String WWW_ASSET_PATH_PREFIX = "file:///android_asset/www/";
|
||||
private static final String NEW_LINE = System.getProperty("line.separator");
|
||||
private static boolean ShouldClearHistoryOnLoad = false;
|
||||
private CordovaWebView mainWebView;
|
||||
private CodePushPackageManager codePushPackageManager;
|
||||
|
@ -90,54 +92,57 @@ public class CodePush extends CordovaPlugin {
|
|||
return execRestartApplication(args, callbackContext);
|
||||
} else if ("getPackageHash".equals(action)) {
|
||||
return execGetPackageHash(args, callbackContext);
|
||||
} else if ("verifySignature".equals(action)) {
|
||||
return execVerifySignature(args, callbackContext);
|
||||
} else if ("decodeSignature".equals(action)) {
|
||||
return execDecodeSignature(args, callbackContext);
|
||||
} else if ("getPublicKey".equals(action)) {
|
||||
return execGetPublicKey(args, callbackContext);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execVerifySignature(final CordovaArgs args, final CallbackContext callbackContext) {
|
||||
private boolean execGetPublicKey(final CordovaArgs args, final CallbackContext callbackContext) {
|
||||
String publicKey = mainWebView.getPreferences().getString(PUBLIC_KEY_PREFERENCE, null);
|
||||
callbackContext.success(publicKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean execDecodeSignature(final CordovaArgs args, final CallbackContext callbackContext) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
String stringPublicKey = mainWebView.getPreferences().getString(PUBLIC_KEY_PREFERENCE, null);
|
||||
|
||||
// bail out early if no public key was configured in config.xml
|
||||
if (stringPublicKey == null) {
|
||||
callbackContext.success((String) null);
|
||||
return null;
|
||||
}
|
||||
|
||||
final PublicKey publicKey;
|
||||
try {
|
||||
publicKey = parsePublicKey(stringPublicKey);
|
||||
} catch (CodePushException e) {
|
||||
callbackContext.error("Error occurred while creating the a public key" + e.getMessage());
|
||||
return null;
|
||||
String stringPublicKey = args.getString(0);
|
||||
|
||||
final PublicKey publicKey;
|
||||
try {
|
||||
publicKey = parsePublicKey(stringPublicKey);
|
||||
} catch (CodePushException e) {
|
||||
callbackContext.error("Error occurred while creating the a public key" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
final String signature = args.getString(1);
|
||||
|
||||
final Map<String, Object> claims;
|
||||
try {
|
||||
claims = verifyAndDecodeJWT(signature, publicKey);
|
||||
} catch (CodePushException e) {
|
||||
callbackContext.error("The update could not be verified because it was not signed by a trusted party. " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
final String contentHash = (String) claims.get("contentHash");
|
||||
if (contentHash == null) {
|
||||
callbackContext.error("The update could not be verified because the signature did not specify a content hash.");
|
||||
return null;
|
||||
}
|
||||
callbackContext.success(contentHash);
|
||||
|
||||
} catch (Exception e) {
|
||||
callbackContext.error("Unknown error occurred during signature decoding. " + e.getMessage());
|
||||
}
|
||||
|
||||
final String signature = getSignature(args);
|
||||
if (signature == null) {
|
||||
callbackContext.error("The update could not be verified because no signature was found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, Object> claims;
|
||||
try {
|
||||
claims = verifyAndDecodeJWT(signature, publicKey);
|
||||
} catch (CodePushException e) {
|
||||
callbackContext.error("The update could not be verified because it was not signed by a trusted party. " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
final String contentHash = (String) claims.get("contentHash");
|
||||
if (contentHash == null) {
|
||||
callbackContext.error("The update could not be verified because the signature did not specify a content hash.");
|
||||
return null;
|
||||
}
|
||||
|
||||
callbackContext.success(contentHash);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
@ -146,13 +151,16 @@ public class CodePush extends CordovaPlugin {
|
|||
|
||||
private PublicKey parsePublicKey(String stringPublicKey) throws CodePushException {
|
||||
try {
|
||||
stringPublicKey = stringPublicKey
|
||||
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replace("-----END PUBLIC KEY-----", "")
|
||||
.replace("
", "") //gradle automatically replaces new line to 

|
||||
.replace(NEW_LINE, "");
|
||||
byte[] byteKey = Base64.decode(stringPublicKey.getBytes(), Base64.DEFAULT);
|
||||
X509EncodedKeySpec X509Key = new X509EncodedKeySpec(byteKey);
|
||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||
return kf.generatePublic(X509Key);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new CodePushException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
} catch (Exception e) {
|
||||
throw new CodePushException(e);
|
||||
}
|
||||
}
|
||||
|
@ -160,9 +168,11 @@ public class CodePush extends CordovaPlugin {
|
|||
private Map<String, Object> verifyAndDecodeJWT(String jwt, PublicKey publicKey) throws CodePushException {
|
||||
try {
|
||||
SignedJWT signedJWT = SignedJWT.parse(jwt);
|
||||
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey)publicKey);
|
||||
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
|
||||
if (signedJWT.verify(verifier)) {
|
||||
return signedJWT.getJWTClaimsSet().getClaims();
|
||||
Map<String, Object> claims = signedJWT.getJWTClaimsSet().getClaims();
|
||||
Utilities.logMessage("JWT verification succeeded, payload content: " + claims.toString());
|
||||
return claims;
|
||||
}
|
||||
throw new CodePushException("JWT verification failed: wrong signature");
|
||||
} catch (Exception e) {
|
||||
|
@ -170,18 +180,6 @@ public class CodePush extends CordovaPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
private String getSignature(CordovaArgs args) {
|
||||
try {
|
||||
if (args.isNull(0)) {
|
||||
return null;
|
||||
} else {
|
||||
return args.getString(0);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execGetBinaryHash(final CallbackContext callbackContext) {
|
||||
String cachedBinaryHash = codePushPackageManager.getCachedBinaryHash();
|
||||
if (cachedBinaryHash == null) {
|
||||
|
@ -192,11 +190,7 @@ public class CodePush extends CordovaPlugin {
|
|||
String binaryHash = UpdateHashUtils.getBinaryHash(cordova.getActivity());
|
||||
codePushPackageManager.saveBinaryHash(binaryHash);
|
||||
callbackContext.success(binaryHash);
|
||||
} catch (IOException e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (Exception e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
}
|
||||
|
||||
|
@ -217,13 +211,7 @@ public class CodePush extends CordovaPlugin {
|
|||
try {
|
||||
String binaryHash = UpdateHashUtils.getHashForPath(cordova.getActivity(), args.getString(0) + "/www");
|
||||
callbackContext.success(binaryHash);
|
||||
} catch (IOException e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
} catch (JSONException e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (Exception e) {
|
||||
callbackContext.error("An error occurred when trying to get the hash of the binary contents. " + e.getMessage());
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ import java.util.Set;
|
|||
public class UpdateHashUtils {
|
||||
private static final Set<String> ignoredFiles = new HashSet<String>(Arrays.asList(
|
||||
".codepushrelease",
|
||||
".DS_Store"
|
||||
".DS_Store",
|
||||
"__MACOSX"
|
||||
));
|
||||
|
||||
public static String getBinaryHash(Activity activity) throws IOException, NoSuchAlgorithmException, ClassNotFoundException {
|
||||
|
|
|
@ -81,6 +81,10 @@ public class Utilities {
|
|||
Log.e(CodePush.class.getName(), "An error occured. " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
public static void logMessage(String message) {
|
||||
Log.e(CodePush.class.getName(), message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getting the full path to all the assets in a given asset path.
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
- (void)restartApplication:(CDVInvokedUrlCommand *)command;
|
||||
- (void)getBinaryHash:(CDVInvokedUrlCommand *)command;
|
||||
- (void)getPackageHash:(CDVInvokedUrlCommand *)command;
|
||||
- (void)verifySignature:(CDVInvokedUrlCommand *)command;
|
||||
- (void)decodeSignature:(CDVInvokedUrlCommand *)command;
|
||||
- (void)getPublicKey:(CDVInvokedUrlCommand *)command;
|
||||
- (void)pluginInitialize;
|
||||
|
||||
@end
|
||||
void CPLog(NSString *formatString, ...);
|
||||
@end
|
||||
|
|
|
@ -71,26 +71,26 @@ StatusReport* rollbackStatusReport = nil;
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)verifySignature:(CDVInvokedUrlCommand *)command {
|
||||
- (void)getPublicKey:(CDVInvokedUrlCommand *)command {
|
||||
NSString *publicKey = ((CDVViewController *) self.viewController).settings[PublicKeyPreference];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
||||
messageAsString:publicKey];
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)decodeSignature:(CDVInvokedUrlCommand *)command {
|
||||
[self.commandDelegate runInBackground:^{
|
||||
NSString *jwt = [command argumentAtIndex:0 withDefault:nil andClass:[NSString class]];
|
||||
NSString *publicKey = ((CDVViewController *) self.viewController).settings[PublicKeyPreference];
|
||||
NSString *publicKey = [command argumentAtIndex:0 withDefault:nil andClass:[NSString class]];
|
||||
|
||||
// bail out early if no public key was configured in config.xml
|
||||
if (!publicKey) {
|
||||
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
|
||||
return;
|
||||
}
|
||||
// remove BEGIN / END tags and line breaks from public key string
|
||||
publicKey = [publicKey stringByReplacingOccurrencesOfString:@"-----BEGIN PUBLIC KEY-----\n"
|
||||
withString:@""];
|
||||
publicKey = [publicKey stringByReplacingOccurrencesOfString:@"-----END PUBLIC KEY-----"
|
||||
withString:@""];
|
||||
publicKey = [publicKey stringByReplacingOccurrencesOfString:@"\n"
|
||||
withString:@""];
|
||||
|
||||
// no .codepushrelease file in the update (or it couldn't be read)
|
||||
if (!jwt || [jwt isEqualToString:@""]) {
|
||||
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
||||
messageAsString:@"\"Error! Public key was provided but there is no JWT signature within app bundle to verify.\n"
|
||||
@"Possible reasons, why that might happen: \n"
|
||||
@"You've released a CodePush bundle update using a version of CodePush CLI that does not support code signing.\n"
|
||||
@"You've released a CodePush bundle update without providing the --privateKeyPath option."]
|
||||
callbackId:command.callbackId];
|
||||
}
|
||||
NSString *jwt = [command argumentAtIndex:1 withDefault:nil andClass:[NSString class]];
|
||||
|
||||
id <JWTAlgorithmDataHolderProtocol> verifyDataHolder = [JWTAlgorithmRSFamilyDataHolder new]
|
||||
.keyExtractorType([JWTCryptoKeyExtractor publicKeyWithPEMBase64].type)
|
||||
|
@ -100,6 +100,7 @@ StatusReport* rollbackStatusReport = nil;
|
|||
JWTCodingResultType *verifyResult = verifyBuilder.result;
|
||||
CDVPluginResult *pluginResult;
|
||||
if (verifyResult.successResult) {
|
||||
CPLog(@"JWT signature verification succeeded, payload content: %@", verifyResult.successResult.payload);
|
||||
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
||||
messageAsString:verifyResult.successResult.payload[@"contentHash"]];
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
+ (NSArray *)ignoredFilenames {
|
||||
return @[
|
||||
@".codepushrelease",
|
||||
@".DS_Store"
|
||||
@".DS_Store",
|
||||
@"__MACOSX"
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -24,4 +24,12 @@
|
|||
return fileDate;
|
||||
}
|
||||
|
||||
@end
|
||||
void CPLog(NSString *formatString, ...) {
|
||||
va_list args;
|
||||
va_start(args, formatString);
|
||||
NSString *prependedFormatString = [NSString stringWithFormat:@"\n[CodePush] %@", formatString];
|
||||
NSLogv(prependedFormatString, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -397,4 +397,12 @@ interface IDiffManifest {
|
|||
interface DownloadProgress {
|
||||
totalBytes: number;
|
||||
receivedBytes: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the result of LocalPackage.handleDeployment execution.
|
||||
*/
|
||||
interface DeploymentResult {
|
||||
deployDir: DirectoryEntry,
|
||||
isDiffUpdate: boolean
|
||||
}
|
|
@ -115,7 +115,16 @@ class FileUtil {
|
|||
FileUtil.directoryExists(cordova.file.dataDirectory, path, callback);
|
||||
}
|
||||
|
||||
public static copyDirectoryEntriesTo(sourceDir: DirectoryEntry, destinationDir: DirectoryEntry, callback: Callback<void>): void {
|
||||
public static copyDirectoryEntriesTo(sourceDir: DirectoryEntry, destinationDir: DirectoryEntry, ignoreList: string[], callback: Callback<void>): void {
|
||||
/*
|
||||
Native-side exception occurs while trying to copy “.DS_Store” and “__MACOSX” entries generated by macOS, so just skip them
|
||||
*/
|
||||
if (ignoreList.indexOf(".DS_Store") === -1){
|
||||
ignoreList.push(".DS_Store");
|
||||
}
|
||||
if (ignoreList.indexOf("__MACOSX") === -1){
|
||||
ignoreList.push("__MACOSX");
|
||||
}
|
||||
|
||||
var fail = (error: FileError) => {
|
||||
callback(FileUtil.fileErrorToError(error), null);
|
||||
|
@ -128,10 +137,7 @@ class FileUtil {
|
|||
if (i < entries.length) {
|
||||
var nextEntry = entries[i++];
|
||||
/* recursively call copyOne on copy success */
|
||||
if (nextEntry.name === ".DS_Store" || nextEntry.name === "__MACOSX") {
|
||||
/*
|
||||
Native-side exception occurs while trying to copy “.DS_Store” and “__MACOSX” entries generated by macOS, so just skip them
|
||||
*/
|
||||
if (ignoreList.indexOf(nextEntry.name) > 0) {
|
||||
copyOne();
|
||||
} else {
|
||||
var entryAlreadyInDestination = (destinationEntry: Entry) => {
|
||||
|
@ -141,7 +147,7 @@ class FileUtil {
|
|||
|
||||
if (destinationEntry.isDirectory) {
|
||||
/* directory */
|
||||
FileUtil.copyDirectoryEntriesTo(<DirectoryEntry>nextEntry, <DirectoryEntry>destinationEntry, (error: Error) => {
|
||||
FileUtil.copyDirectoryEntriesTo(<DirectoryEntry>nextEntry, <DirectoryEntry>destinationEntry, ignoreList, (error: Error) => {
|
||||
if (error) {
|
||||
callback(error, null);
|
||||
} else {
|
||||
|
|
|
@ -66,23 +66,25 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
|
||||
var newPackageLocation = LocalPackage.VersionsDir + "/" + this.packageHash;
|
||||
|
||||
var signatureVerified = (deployDir: DirectoryEntry) => {
|
||||
this.localPath = deployDir.fullPath;
|
||||
this.finishInstall(deployDir, installOptions, installSuccess, installError);
|
||||
};
|
||||
|
||||
var donePackageFileCopy = (deployDir: DirectoryEntry) => {
|
||||
this.verifyPackage(deployDir, installError, CodePushUtil.getNodeStyleCallbackFor<DirectoryEntry>(signatureVerified, installError))
|
||||
};
|
||||
|
||||
var newPackageUnzipped = function (unzipError: Error) {
|
||||
if (unzipError) {
|
||||
installError && installError(new Error("Could not unzip package" + CodePushUtil.getErrorMessage(unzipError)));
|
||||
} else {
|
||||
LocalPackage.handleDeployment(newPackageLocation, CodePushUtil.getNodeStyleCallbackFor<DirectoryEntry>(donePackageFileCopy, installError));
|
||||
LocalPackage.handleDeployment(newPackageLocation, CodePushUtil.getNodeStyleCallbackFor<DeploymentResult>(donePackageFileCopy, installError));
|
||||
}
|
||||
};
|
||||
|
||||
var donePackageFileCopy = (deploymentResult: DeploymentResult) => {
|
||||
this.verifyPackage(deploymentResult, installError, () => {
|
||||
packageVerified(deploymentResult.deployDir);
|
||||
});
|
||||
};
|
||||
|
||||
var packageVerified = (deployDir: DirectoryEntry) => {
|
||||
this.localPath = deployDir.fullPath;
|
||||
this.finishInstall(deployDir, installOptions, installSuccess, installError);
|
||||
};
|
||||
|
||||
FileUtil.getDataDirectory(LocalPackage.DownloadUnzipDir, false, (error: Error, directoryEntry: DirectoryEntry) => {
|
||||
var unzipPackage = () => {
|
||||
FileUtil.getDataDirectory(LocalPackage.DownloadUnzipDir, true, (innerError: Error, unzipDir: DirectoryEntry) => {
|
||||
|
@ -112,54 +114,150 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
}
|
||||
}
|
||||
|
||||
private verifyPackage(unzipDir: DirectoryEntry, installError: ErrorCallback, callback: Callback<DirectoryEntry>): void {
|
||||
var packageHashSuccess = (localHash: string) => {
|
||||
CodePushUtil.logMessage("Expected hash: " + this.packageHash + ", actual hash: " + localHash);
|
||||
FileUtil.readFile(cordova.file.dataDirectory, unzipDir.fullPath + '/www', '.codepushrelease', (error, contents) => {
|
||||
var verifySignatureSuccess = (expectedHash?: string) => {
|
||||
// first, we always compare the hash we just calculated to the packageHash reported from the server
|
||||
if (localHash !== this.packageHash) {
|
||||
installError(new Error("package hash verification failed"));
|
||||
return;
|
||||
private verifyPackage(deploymentResult: DeploymentResult, installError: ErrorCallback, successCallback: SuccessCallback<void>): void {
|
||||
|
||||
var deployDir = deploymentResult.deployDir;
|
||||
|
||||
var verificationFail: ErrorCallback = (error: Error) => {
|
||||
installError && installError(error);
|
||||
};
|
||||
|
||||
var verify = (isSignatureVerificationEnabled: boolean, isSignatureAppearedInBundle: boolean, publicKey: string, signature: string) => {
|
||||
if (isSignatureVerificationEnabled) {
|
||||
if (isSignatureAppearedInBundle) {
|
||||
this.verifyHash(deployDir, this.packageHash, verificationFail, () => {
|
||||
this.verifySignature(deployDir, this.packageHash, publicKey, signature, verificationFail, successCallback);
|
||||
});
|
||||
} else {
|
||||
var errorMessage =
|
||||
"Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
|
||||
"Possible reasons, why that might happen: \n" +
|
||||
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
|
||||
"2. You've been released CodePush bundle update without providing --privateKeyPath option.";
|
||||
installError && installError(new Error(errorMessage));
|
||||
}
|
||||
} else {
|
||||
if (isSignatureAppearedInBundle) {
|
||||
CodePushUtil.logMessage(
|
||||
"Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
|
||||
"Please ensure that public key is properly configured within your application."
|
||||
);
|
||||
|
||||
//verifyHash
|
||||
this.verifyHash(deployDir, this.packageHash, verificationFail, successCallback);
|
||||
} else {
|
||||
if (deploymentResult.isDiffUpdate){
|
||||
//verifyHash
|
||||
this.verifyHash(deployDir, this.packageHash, verificationFail, successCallback);
|
||||
}
|
||||
successCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this happens if (and only if) no public key is available in config.xml
|
||||
// -> no code signing
|
||||
if (!expectedHash) {
|
||||
CodePushUtil.logMessage("The update contents succeeded the data integrity check.");
|
||||
callback(null, unzipDir);
|
||||
if (deploymentResult.isDiffUpdate){
|
||||
CodePushUtil.logMessage("Applying diff update");
|
||||
} else {
|
||||
CodePushUtil.logMessage("Applying full update");
|
||||
}
|
||||
|
||||
// .codepushrelease was read but there is no public key in config.xml
|
||||
if (contents != null) {
|
||||
CodePushUtil.logMessage("Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. \n" +
|
||||
"Please ensure that a public key is properly configured within your application.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var isSignatureVerificationEnabled: boolean, isSignatureAppearedInBundle: boolean;
|
||||
var publicKey: string;
|
||||
|
||||
// code signing is active, only proceed if the locally computed hash is the same as the one decoded from the JWT
|
||||
if (localHash === expectedHash) {
|
||||
CodePushUtil.logMessage("The update contents succeeded the code signing check.");
|
||||
callback(null, unzipDir);
|
||||
return;
|
||||
}
|
||||
this.getPublicKey((error, publicKeyResult) => {
|
||||
if (error) {
|
||||
installError && installError(new Error("Error reading public key. " + error));
|
||||
return;
|
||||
}
|
||||
|
||||
installError(new Error("The update contents failed the code signing check."));
|
||||
};
|
||||
var verifySignatureFail = (error: string) => {
|
||||
installError && installError(new Error("The update contents failed the code signing check. " + error));
|
||||
};
|
||||
CodePushUtil.logMessage("Verifying signature for folder path: " + unzipDir.fullPath);
|
||||
cordova.exec(verifySignatureSuccess, verifySignatureFail, "CodePush", "verifySignature", [contents]);
|
||||
publicKey = publicKeyResult;
|
||||
isSignatureVerificationEnabled = (publicKey !== null);
|
||||
|
||||
this.getSignatureFromUpdate(deploymentResult.deployDir, (error, signature) => {
|
||||
if (error) {
|
||||
installError && installError(new Error("Error reading signature from update. " + error));
|
||||
return;
|
||||
}
|
||||
|
||||
isSignatureAppearedInBundle = (signature !== null);
|
||||
|
||||
verify(isSignatureVerificationEnabled, isSignatureAppearedInBundle, publicKey, signature);
|
||||
});
|
||||
};
|
||||
var packageHashFail = (error: string) => {
|
||||
installError && installError(new Error("unable to compute hash for package: " + error));
|
||||
};
|
||||
CodePushUtil.logMessage("Verifying hash for folder path: " + unzipDir.fullPath);
|
||||
cordova.exec(packageHashSuccess, packageHashFail,"CodePush","getPackageHash",[unzipDir.fullPath]);
|
||||
});
|
||||
}
|
||||
|
||||
private getPublicKey(callback: Callback<string>) {
|
||||
|
||||
var success = (publicKey: string) => {
|
||||
callback(null, publicKey);
|
||||
}
|
||||
|
||||
var fail = (error: Error) => {
|
||||
callback(error, null);
|
||||
}
|
||||
|
||||
cordova.exec(success, fail,"CodePush","getPublicKey",[]);
|
||||
}
|
||||
|
||||
private getSignatureFromUpdate(deployDir: DirectoryEntry, callback: Callback<string>){
|
||||
|
||||
var rootUri = cordova.file.dataDirectory;
|
||||
var path = deployDir.fullPath + '/www';
|
||||
var fileName = '.codepushrelease';
|
||||
|
||||
FileUtil.fileExists(rootUri, path, fileName, (error, result) => {
|
||||
if (!result) {
|
||||
// signature absents in the bundle
|
||||
callback(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtil.readFile(rootUri, path, fileName, (error, signature) => {
|
||||
if (error) {
|
||||
//error reading signature file from bundle
|
||||
callback(error, null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, signature);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private verifyHash(deployDir: DirectoryEntry, newUpdateHash: string, errorCallback: ErrorCallback, successCallback: SuccessCallback<void>){
|
||||
var packageHashSuccess = (computedHash: string) => {
|
||||
if (computedHash !== newUpdateHash) {
|
||||
errorCallback(new Error("The update contents failed the data integrity check."));
|
||||
return;
|
||||
}
|
||||
|
||||
CodePushUtil.logMessage("The update contents succeeded the data integrity check.");
|
||||
successCallback();
|
||||
}
|
||||
var packageHashFail = (error: Error) => {
|
||||
errorCallback(new Error("Unable to compute hash for package: " + error));
|
||||
}
|
||||
CodePushUtil.logMessage("Verifying hash for folder path: " + deployDir.fullPath);
|
||||
cordova.exec(packageHashSuccess, packageHashFail, "CodePush", "getPackageHash", [deployDir.fullPath]);
|
||||
}
|
||||
|
||||
private verifySignature(deployDir: DirectoryEntry, newUpdateHash: string, publicKey: string, signature: string, errorCallback: ErrorCallback, successCallback: SuccessCallback<void>){
|
||||
var decodeSignatureSuccess = (contentHash: string) => {
|
||||
if (contentHash !== newUpdateHash) {
|
||||
errorCallback(new Error("The update contents failed the code signing check."));
|
||||
return;
|
||||
}
|
||||
|
||||
CodePushUtil.logMessage("The update contents succeeded the code signing check.");
|
||||
successCallback();
|
||||
}
|
||||
var decodeSignatureFail = (error: Error) => {
|
||||
errorCallback(new Error("Unable to verify signature for package: " + error));
|
||||
}
|
||||
CodePushUtil.logMessage("Verifying signature for folder path: " + deployDir.fullPath);
|
||||
cordova.exec(decodeSignatureSuccess, decodeSignatureFail, "CodePush", "decodeSignature", [publicKey, signature]);
|
||||
}
|
||||
|
||||
private finishInstall(deployDir: DirectoryEntry, installOptions: InstallOptions, installSuccess: SuccessCallback<InstallMode>, installError: ErrorCallback): void {
|
||||
function backupPackageInformationFileIfNeeded(backupIfNeededDone: Callback<void>) {
|
||||
NativeAppInfo.isPendingUpdate((pendingUpdate: boolean) => {
|
||||
|
@ -212,7 +310,7 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
}, installError);
|
||||
}
|
||||
|
||||
private static handleDeployment(newPackageLocation: string, deployCallback: Callback<DirectoryEntry>): void {
|
||||
private static handleDeployment(newPackageLocation: string, deployCallback: Callback<DeploymentResult>): void {
|
||||
FileUtil.getDataDirectory(newPackageLocation, true, (deployDirError: Error, deployDir: DirectoryEntry) => {
|
||||
// check for diff manifest
|
||||
FileUtil.getDataFile(LocalPackage.DownloadUnzipDir, LocalPackage.DiffManifestFile, false, (manifestError: Error, diffManifest: FileEntry) => {
|
||||
|
@ -220,7 +318,7 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
LocalPackage.handleDiffDeployment(newPackageLocation, diffManifest, deployCallback);
|
||||
} else {
|
||||
LocalPackage.handleCleanDeployment(newPackageLocation, (error: Error) => {
|
||||
deployCallback(error, deployDir);
|
||||
deployCallback(error, {deployDir, isDiffUpdate: false});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -253,18 +351,18 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
});
|
||||
}
|
||||
|
||||
private static handleCleanDeployment(newPackageLocation: string, cleanDeployCallback: Callback<DirectoryEntry>): void {
|
||||
private static handleCleanDeployment(newPackageLocation: string, cleanDeployCallback: Callback<DeploymentResult>): void {
|
||||
// no diff manifest
|
||||
FileUtil.getDataDirectory(newPackageLocation, true, (deployDirError: Error, deployDir: DirectoryEntry) => {
|
||||
FileUtil.getDataDirectory(LocalPackage.DownloadUnzipDir, false, (unzipDirErr: Error, unzipDir: DirectoryEntry) => {
|
||||
if (unzipDirErr || deployDirError) {
|
||||
cleanDeployCallback(new Error("Could not copy new package."), null);
|
||||
} else {
|
||||
FileUtil.copyDirectoryEntriesTo(unzipDir, deployDir, (copyError: Error) => {
|
||||
FileUtil.copyDirectoryEntriesTo(unzipDir, deployDir, [/*no need to ignore copy anything*/], (copyError: Error) => {
|
||||
if (copyError) {
|
||||
cleanDeployCallback(copyError, null);
|
||||
} else {
|
||||
cleanDeployCallback(null, deployDir);
|
||||
cleanDeployCallback(null, {deployDir, isDiffUpdate: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -272,7 +370,7 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
});
|
||||
}
|
||||
|
||||
private static copyCurrentPackage(newPackageLocation: string, copyCallback: Callback<void>): void {
|
||||
private static copyCurrentPackage(newPackageLocation: string, ignoreList: string[], copyCallback: Callback<void>): void {
|
||||
var handleError = (e: Error) => {
|
||||
copyCallback && copyCallback(e, null);
|
||||
};
|
||||
|
@ -296,7 +394,7 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
handleError(new Error("Could not acquire the source/destination folders. "));
|
||||
} else {
|
||||
var success = (currentPackageDirectory: DirectoryEntry) => {
|
||||
FileUtil.copyDirectoryEntriesTo(currentPackageDirectory, deployDir, copyCallback);
|
||||
FileUtil.copyDirectoryEntriesTo(currentPackageDirectory, deployDir, ignoreList, copyCallback);
|
||||
};
|
||||
|
||||
var fail = (fileSystemError: FileError) => {
|
||||
|
@ -319,13 +417,13 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
LocalPackage.getPackage(LocalPackage.PackageInfoFile, packageSuccess, packageFailure);
|
||||
}
|
||||
|
||||
private static handleDiffDeployment(newPackageLocation: string, diffManifest: FileEntry, diffCallback: Callback<DirectoryEntry>): void {
|
||||
private static handleDiffDeployment(newPackageLocation: string, diffManifest: FileEntry, diffCallback: Callback<DeploymentResult>): void {
|
||||
var handleError = (e: Error) => {
|
||||
diffCallback(e, null);
|
||||
};
|
||||
|
||||
/* copy old files */
|
||||
LocalPackage.copyCurrentPackage(newPackageLocation, (currentPackageError: Error) => {
|
||||
/* copy old files except signature file */
|
||||
LocalPackage.copyCurrentPackage(newPackageLocation, [".codepushrelease"], (currentPackageError: Error) => {
|
||||
/* copy new files */
|
||||
LocalPackage.handleCleanDeployment(newPackageLocation, (cleanDeployError: Error) => {
|
||||
/* delete files mentioned in the manifest */
|
||||
|
@ -339,7 +437,7 @@ class LocalPackage extends Package implements ILocalPackage {
|
|||
if (deleteError || deployDirError) {
|
||||
handleError(new Error("Cannot clean up deleted manifest files."));
|
||||
} else {
|
||||
diffCallback(null, deployDir);
|
||||
diffCallback(null, {deployDir, isDiffUpdate: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче