Skip to content

Commit

Permalink
Merge pull request #44645 from m4gr3d/update_apk_signing_logic
Browse files Browse the repository at this point in the history
Update the logic to sign prebuilt Godot Android apks
  • Loading branch information
akien-mga authored Jan 1, 2021
2 parents c0846b6 + 49969de commit 3433780
Showing 1 changed file with 130 additions and 80 deletions.
210 changes: 130 additions & 80 deletions platform/android/export/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

// Check for devices updates
String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();
if (FileAccess::exists(adb)) {
String devices;
List<String> args;
Expand Down Expand Up @@ -425,7 +425,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();
if (!FileAccess::exists(adb)) {
return; //adb not configured
}
Expand Down Expand Up @@ -1752,7 +1752,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {

EditorProgress ep("run", "Running on " + devices[p_device].name, 3);

String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();

// Export_temp APK.
if (ep.step("Exporting APK...", 0)) {
Expand Down Expand Up @@ -1899,6 +1899,55 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return run_icon;
}

static String get_adb_path() {
String exe_ext = "";
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".exe";
}
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
return sdk_path.plus_file("platform-tools/adb" + exe_ext);
}

static String get_apksigner_path() {
String exe_ext = "";
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".bat";
}
String apksigner_command_name = "apksigner" + exe_ext;
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
String apksigner_path = "";

Error errn;
String build_tools_dir = sdk_path.plus_file("build-tools");
DirAccessRef da = DirAccess::open(build_tools_dir, &errn);
if (errn != OK) {
print_error("Unable to open Android 'build-tools' directory.");
return apksigner_path;
}

// There are additional versions directories we need to go through.
da->list_dir_begin();
String sub_dir = da->get_next();
while (!sub_dir.is_empty()) {
if (!sub_dir.begins_with(".") && da->current_is_dir()) {
// Check if the tool is here.
String tool_path = build_tools_dir.plus_file(sub_dir).plus_file(apksigner_command_name);
if (FileAccess::exists(tool_path)) {
apksigner_path = tool_path;
break;
}
}
sub_dir = da->get_next();
}
da->list_dir_end();

if (apksigner_path.is_empty()) {
EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool."));
}

return apksigner_path;
}

virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override {
String err;
bool valid = false;
Expand Down Expand Up @@ -1934,25 +1983,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
} else {
valid = exists_export_template("android_source.zip", &err);

if (!FileAccess::exists("res://android/build/build.gradle")) {
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
valid = false;
}
}
r_missing_templates = !valid;

// Validate the rest of the configuration.

String adb = EditorSettings::get_singleton()->get("export/android/adb");

if (!FileAccess::exists(adb)) {
valid = false;
err += TTR("ADB executable not configured in the Editor Settings.") + "\n";
}

String js = EditorSettings::get_singleton()->get("export/android/jarsigner");

if (!FileAccess::exists(js)) {
valid = false;
err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n";
}

String dk = p_preset->get("keystore/debug");

if (!FileAccess::exists(dk)) {
Expand All @@ -1970,25 +2010,45 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
}

if (bool(p_preset->get("custom_template/use_custom_build"))) {
String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path");
if (sdk_path == "") {
err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n";
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
if (sdk_path == "") {
err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n";
valid = false;
} else {
Error errn;
// Check for the platform-tools directory.
DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'platform-tools' directory!");
err += "\n";
valid = false;
}

// Validate that adb is available
String adb_path = get_adb_path();
if (!FileAccess::exists(adb_path)) {
err += TTR("Unable to find Android SDK platform-tools' adb command.");
err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
err += "\n";
valid = false;
} else {
Error errn;
// Check for the platform-tools directory.
DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path for custom build in Editor Settings.");
err += TTR("Missing 'platform-tools' directory!");
err += "\n";
valid = false;
}
}

if (!FileAccess::exists("res://android/build/build.gradle")) {
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
// Check for the build-tools directory.
DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'build-tools' directory!");
err += "\n";
valid = false;
}

// Validate that apksigner is available
String apksigner_path = get_apksigner_path();
if (!FileAccess::exists(apksigner_path)) {
err += TTR("Unable to find Android SDK build-tools' apksigner command.");
err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
err += "\n";
valid = false;
}
}
Expand Down Expand Up @@ -2171,16 +2231,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}

Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String export_path, EditorProgress ep) {
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) {
int export_format = int(p_preset->get("custom_template/export_format"));
String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK";
String release_keystore = p_preset->get("keystore/release");
String release_username = p_preset->get("keystore/release_user");
String release_password = p_preset->get("keystore/release_password");

String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
if (!FileAccess::exists(jarsigner)) {
EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting " + export_label + " is unsigned.");
String apksigner = get_apksigner_path();
if (!FileAccess::exists(apksigner)) {
EditorNode::add_io_error("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting " + export_label + " is unsigned.");
return OK;
}

Expand All @@ -2198,7 +2258,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}

if (ep.step("Signing debug " + export_label + "...", 103)) {
if (ep.step("Signing debug " + export_label + "...", 104)) {
return ERR_SKIP;
}

Expand All @@ -2207,7 +2267,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
password = release_password;
user = release_username;

if (ep.step("Signing release " + export_label + "...", 103)) {
if (ep.step("Signing release " + export_label + "...", 104)) {
return ERR_SKIP;
}
}
Expand All @@ -2218,43 +2278,34 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

List<String> args;
args.push_back("-digestalg");
args.push_back("SHA-256");
args.push_back("-sigalg");
args.push_back("SHA256withRSA");
String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
if (tsa_url != "") {
args.push_back("-tsa");
args.push_back(tsa_url);
}
args.push_back("-verbose");
args.push_back("-keystore");
args.push_back("sign");
args.push_back("--verbose");
args.push_back("--ks");
args.push_back(keystore);
args.push_back("-storepass");
args.push_back(password);
args.push_back(export_path);
args.push_back("--ks-pass");
args.push_back("pass:" + password);
args.push_back("--ks-key-alias");
args.push_back(user);
args.push_back(export_path);
int retval;
OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval);
if (retval) {
EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
EditorNode::add_io_error("'apksigner' returned with error #" + itos(retval));
return ERR_CANT_CREATE;
}

if (ep.step("Verifying " + export_label + "...", 104)) {
if (ep.step("Verifying " + export_label + "...", 105)) {
return ERR_SKIP;
}

args.clear();
args.push_back("-verify");
args.push_back("-keystore");
args.push_back(keystore);
args.push_back("verify");
args.push_back("--verbose");
args.push_back(export_path);
args.push_back("-verbose");

OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval);
if (retval) {
EditorNode::add_io_error("'jarsigner' verification of " + export_label + " failed. Make sure to use a jarsigner from OpenJDK 8.");
EditorNode::add_io_error("'apksigner' verification of " + export_label + " failed.");
return ERR_CANT_CREATE;
}
return OK;
Expand Down Expand Up @@ -2337,8 +2388,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return ERR_UNCONFIGURED;
}
}
String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
String sdk_path = EDITOR_GET("export/android/android_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'.");

// TODO: should we use "package/name" or "application/config/name"?
String project_name = get_project_name(p_preset->get("package/name"));
Expand Down Expand Up @@ -2685,18 +2736,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
CLEANUP_AND_RETURN(err);
}

if (should_sign) {
err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep);
if (err != OK) {
CLEANUP_AND_RETURN(err);
}
}

// Let's zip-align (must be done after signing)
// Let's zip-align (must be done before signing)

static const int ZIP_ALIGNMENT = 4;

if (ep.step("Aligning APK...", 105)) {
// If we're not signing the apk, then the next step should be the last.
const int next_step = should_sign ? 103 : 105;
if (ep.step("Aligning APK...", next_step)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}

Expand Down Expand Up @@ -2770,6 +2816,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
zipClose(final_apk, nullptr);
unzClose(tmp_unaligned);

if (should_sign) {
// Signing must be done last as any additional modifications to the
// file will invalidate the signature.
err = sign_apk(p_preset, p_debug, p_path, ep);
if (err != OK) {
CLEANUP_AND_RETURN(err);
}
}

CLEANUP_AND_RETURN(OK);
}

Expand Down Expand Up @@ -2809,19 +2864,14 @@ void register_android_exporter() {
exe_ext = "*.exe";
}

EDITOR_DEF("export/android/adb", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/adb", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
EDITOR_DEF("export/android/jarsigner", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
EDITOR_DEF("export/android/android_sdk_path", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/debug_keystore", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
EDITOR_DEF("export/android/debug_keystore_pass", "android");
EDITOR_DEF("export/android/force_system_user", false);
EDITOR_DEF("export/android/custom_build_sdk_path", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR));

EDITOR_DEF("export/android/timestamping_authority_url", "");
EDITOR_DEF("export/android/shutdown_adb_on_exit", true);

Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid));
Expand Down

0 comments on commit 3433780

Please sign in to comment.