Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -270,30 +270,26 @@ public void downloadPackage(JSONObject updatePackage, String expectedBundleFileN
String signaturePath = CodePushUpdateUtils.getSignatureFilePath(newUpdateFolderPath);
boolean isSignatureAppearedInBundle = FileUtils.fileAtPathExists(signaturePath);

if (isSignatureVerificationEnabled && !isSignatureAppearedInBundle) {
throw new CodePushInvalidUpdateException(
"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."
);
}

if (!isSignatureVerificationEnabled && isSignatureAppearedInBundle) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How likely it is in the wild? Can you release an app without signing it to the store (if not, that app should in practice always have a public key)?

@ofalvai ofalvai Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't about code signing the native app itself, it's just the CodePush OTA upload. And CodePush updates are not code-signed by default, we don't enforce this. Other products also don't enfoce code signing, in fact, Expo limits this feature to their highest paid tiers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will provide the stringPublicKey used for the isSignatureVerificationEnabled?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from here:

Essentially, this is coming from the app's strings.xml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the public key in strings.xml and Info.plist is optional or mandatory?

CodePushUtils.log(
"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."
);
}

CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);

@ofalvai ofalvai Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acceptable, the non-ZIP update codepath is considered legacy, we don't recommend generating plain JS bundle updates (fortunately, only around 1% of our users do). It's not just the integrity check that's missing from this codepath, but also code signing.


if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
CodePushUpdateUtils.verifyUpdateSignature(newUpdateFolderPath, newUpdateHash, stringPublicKey);
} else {
throw new CodePushInvalidUpdateException(
"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."
);
}
} else {
if (isSignatureAppearedInBundle) {
CodePushUtils.log(
"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."
);
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
} else {
if (isDiffUpdate) {
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
}
}
CodePushUpdateUtils.verifyUpdateSignature(newUpdateFolderPath, newUpdateHash, stringPublicKey);
}

CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
Expand Down
29 changes: 29 additions & 0 deletions code-push-plugin-testing-framework/script/serverUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function setupServer(targetPlatform) {
});
app.get("/v0.1/public/codepush/update_check", function (req, res) {
exports.updateCheckCallback && exports.updateCheckCallback(req);
applyKnownPackageHash();
res.send(exports.updateResponse);
console.log("Update check called from the app.");
console.log("Request: " + JSON.stringify(req.query));
Expand Down Expand Up @@ -53,6 +54,34 @@ function setupServer(targetPlatform) {
exports.server = app.listen(+targetPlatform.getServerUrl().match(serverPortRegEx)[1]);
}
exports.setupServer = setupServer;
/**
* The real content hash of the update archive most recently built during this test scenario.
*/
var knownPackageHash;
var _updatePackagePath;
Object.defineProperty(exports, "updatePackagePath", {
enumerable: true,
configurable: true,
get: function () { return _updatePackagePath; },
set: function (value) {
_updatePackagePath = value;
applyKnownPackageHash();
}
});
/**
* Records the real content hash for the update archive that will be served next, so that
* any update_check response gets the matching package_hash instead of the one filled in
* by default.
*/
function setKnownPackageHash(packageHash) {
knownPackageHash = packageHash;
}
exports.setKnownPackageHash = setKnownPackageHash;
function applyKnownPackageHash() {
if (knownPackageHash && exports.updateResponse && exports.updateResponse.update_info) {
exports.updateResponse.update_info.package_hash = knownPackageHash;
}
}
/**
* Closes the server.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ declare module 'code-push-plugin-testing-framework/script/serverUtil' {
* Closes the server.
*/
export function cleanupServer(): void;
/**
* Records the real content hash for the update archive that will be served next, so the
* next update_check response gets a matching package_hash. Pass a falsy packageHash to
* clear it.
*/
export function setKnownPackageHash(packageHash: string): void;
/**
* Class used to mock the codePush.checkForUpdate() response from the server.
*/
Expand Down
98 changes: 40 additions & 58 deletions ios/CodePush/CodePushPackage.m
Original file line number Diff line number Diff line change
Expand Up @@ -243,69 +243,51 @@ + (void)downloadPackage:(NSDictionary *)updatePackage
NSString *signatureFilePath = [CodePushUpdateUtils getSignatureFilePath:newUpdateFolderPath];
BOOL isSignatureAppearedInBundle = [[NSFileManager defaultManager] fileExistsAtPath:signatureFilePath];

if (isSignatureVerificationEnabled && !isSignatureAppearedInBundle) {
error = [CodePushErrorUtils errorWithMessage:
@"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."];
failCallback(error);
return;
}

if (!isSignatureVerificationEnabled && isSignatureAppearedInBundle) {
CPLog(@"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.");
}

BOOL isHashValid = [CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath
expectedHash:newUpdateHash
error:&error];
if (!isHashValid) {
CPLog(@"The update contents failed the data integrity check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed the data integrity check."];
}

failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the data integrity check.");
}

if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
if (![CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath
expectedHash:newUpdateHash
error:&error]) {
CPLog(@"The update contents failed the data integrity check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed the data integrity check."];
}

failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the data integrity check.");
BOOL isSignatureValid = [CodePushUpdateUtils verifyUpdateSignatureFor:newUpdateFolderPath
expectedHash:newUpdateHash
withPublicKey:publicKey
error:&error];
if (!isSignatureValid) {
CPLog(@"The update contents failed code signing check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed code signing check."];
}
BOOL isSignatureValid = [CodePushUpdateUtils verifyUpdateSignatureFor:newUpdateFolderPath
expectedHash:newUpdateHash
withPublicKey:publicKey
error:&error];
if (!isSignatureValid) {
CPLog(@"The update contents failed code signing check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed code signing check."];
}
failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the code signing check.");
}
} else {
error = [CodePushErrorUtils errorWithMessage:
@"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."];
failCallback(error);
return;
}

} else {
BOOL needToVerifyHash;
if (isSignatureAppearedInBundle) {
CPLog(@"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.");
needToVerifyHash = true;
} else {
needToVerifyHash = isDiffUpdate;
}
if(needToVerifyHash){
if (![CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath
expectedHash:newUpdateHash
error:&error]) {
CPLog(@"The update contents failed the data integrity check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed the data integrity check."];
}

failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the data integrity check.");
}
CPLog(@"The update contents succeeded the code signing check.");
}
}
} else {
Expand Down
Loading