From 942f8d3be95f2ea1a359a7ba33babaef4801c6dc Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:57:18 +0200 Subject: [PATCH] Implement support for PCK signing. --- core/SCsub | 38 +- core/crypto/SCsub | 33 +- core/crypto/crypto_core.cpp | 105 ++++++ core/crypto/crypto_core.h | 49 +++ core/io/file_access_pack.cpp | 203 ++++++++-- core/io/file_access_pack.h | 31 +- core/io/file_access_zip.cpp | 2 +- core/io/pck_packer.compat.inc | 41 ++ core/io/pck_packer.cpp | 94 ++++- core/io/pck_packer.h | 11 +- core/object/script_language.h | 2 - doc/classes/PCKPacker.xml | 12 +- editor/export/editor_export.cpp | 30 +- editor/export/editor_export_platform.cpp | 232 +++++++++--- editor/export/editor_export_platform.h | 19 +- editor/export/editor_export_preset.cpp | 58 ++- editor/export/editor_export_preset.h | 32 +- editor/export/project_export.cpp | 351 ++++++++++++++++-- editor/export/project_export.h | 33 +- .../4.2-stable.expected | 7 + modules/gdscript/register_types.cpp | 16 +- platform/android/export/export_plugin.cpp | 4 +- platform/android/export/export_plugin.h | 6 +- .../android/export/gradle_export_util.cpp | 2 +- platform/android/export/gradle_export_util.h | 2 +- .../include/godot_core_ecdsa_mbedtls_config.h | 75 ++++ 26 files changed, 1316 insertions(+), 172 deletions(-) create mode 100644 core/io/pck_packer.compat.inc create mode 100644 thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h diff --git a/core/SCsub b/core/SCsub index 3b1a7ca79a1dde..584ff0cc060b6c 100644 --- a/core/SCsub +++ b/core/SCsub @@ -7,12 +7,17 @@ import core_builders env.core_sources = [] -# Generate AES256 script encryption key +# Generate AES256 PCK encryption key and ECDSA PCK signature public key. import os +import base64 txt = "0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0" -if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: - key = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"] +if ("SCRIPT_AES256_ENCRYPTION_KEY" in os.environ) or ("PCK_AES256_ENCRYPTION_KEY" in os.environ): + key = "" + if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: + key = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"] + elif "PCK_AES256_ENCRYPTION_KEY" in os.environ: + key = os.environ["PCK_AES256_ENCRYPTION_KEY"] ec_valid = True if len(key) != 64: ec_valid = False @@ -30,15 +35,38 @@ if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: if not ec_valid: print("Error: Invalid AES256 encryption key, not 64 hexadecimal characters: '" + key + "'.") print( - "Unset 'SCRIPT_AES256_ENCRYPTION_KEY' in your environment " + "Unset 'PCK_AES256_ENCRYPTION_KEY' in your environment " "or make sure that it contains exactly 64 hexadecimal characters." ) Exit(255) +pk_len = 1 +pk_txt = "0" +if "PCK_SIGNING_PUBLIC_KEY" in os.environ: + pub_key = os.environ["PCK_SIGNING_PUBLIC_KEY"] + key = base64.b64decode(pub_key).hex() + + pk_txt = "" + pk_len = len(key) >> 1 + for i in range(pk_len): + if i > 0: + pk_txt += "," + pk_txt += "0x" + key[i * 2 : i * 2 + 2] + env.Append(CPPDEFINES=["PCK_SIGNING_ENABLED"]) + # NOTE: It is safe to generate this file here, since this is still executed serially with open("script_encryption_key.gen.cpp", "w") as f: - f.write('#include "core/config/project_settings.h"\nuint8_t script_encryption_key[32]={' + txt + "};\n") + f.write('#include "core/config/project_settings.h"\n') + f.write("uint8_t pck_encryption_key[32]={" + txt + "};\n") + f.write("uint8_t pck_sign_pub_key[" + str(pk_len) + "]={" + pk_txt + "};\n") + +with open("script_encryption_key.gen.h", "w") as f: + f.write("extern uint8_t pck_encryption_key[32];\n") + f.write("extern uint8_t pck_sign_pub_key[" + str(pk_len) + "];\n") + f.write("size_t pck_sign_pub_key_len=" + str(pk_len) + ";\n") +if env["module_mbedtls_enabled"] or env.editor_build or "PCK_SIGNING_PUBLIC_KEY" in os.environ: + env.Append(CPPDEFINES=["ECDSA_ENABLED"]) # Add required thirdparty code. diff --git a/core/crypto/SCsub b/core/crypto/SCsub index ac79e10d19a429..65b7e4ddde8daf 100644 --- a/core/crypto/SCsub +++ b/core/crypto/SCsub @@ -2,6 +2,8 @@ Import("env") +import os + env_crypto = env.Clone() is_builtin = env["builtin_mbedtls"] @@ -21,9 +23,14 @@ if is_builtin or not has_module: # to make a "light" build with only the necessary mbedtls files. if not has_module: # Minimal mbedTLS config file - env_crypto.Append( - CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')] - ) + if env.editor_build or "PCK_SIGNING_PUBLIC_KEY" in os.environ: + env_crypto.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h\\"')] + ) + else: + env_crypto.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')] + ) # Build minimal mbedTLS library (MD5/SHA/Base64/AES). env_thirdparty = env_crypto.Clone() env_thirdparty.disable_warnings() @@ -39,10 +46,28 @@ if not has_module: "sha256.c", "godot_core_mbedtls_platform.c", ] + if env.editor_build or "PCK_SIGNING_PUBLIC_KEY" in os.environ: + # PCK siginig is enabled or with editor. + thirdparty_mbedtls_sources += [ + "asn1parse.c", + "asn1write.c", + "bignum.c", + "ecdsa.c", + "ecp.c", + "ecp_curves.c", + "hmac_drbg.c", + "md.c", + "platform.c", + "platform_util.c", + "threading.c", + ] + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h") + else: + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h") + thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources] env_thirdparty.add_source_files(thirdparty_obj, thirdparty_mbedtls_sources) # Needed to force rebuilding the library when the configuration file is updated. - env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h") env.core_sources += thirdparty_obj elif is_builtin: # Module mbedTLS config file diff --git a/core/crypto/crypto_core.cpp b/core/crypto/crypto_core.cpp index 17b34c08e2b05c..9d60827804836c 100644 --- a/core/crypto/crypto_core.cpp +++ b/core/crypto/crypto_core.cpp @@ -40,6 +40,9 @@ #include #include +#include +#include + // RandomGenerator CryptoCore::RandomGenerator::RandomGenerator() { entropy = memalloc(sizeof(mbedtls_entropy_context)); @@ -246,3 +249,105 @@ Error CryptoCore::sha256(const uint8_t *p_src, int p_src_len, unsigned char r_ha int ret = mbedtls_sha256_ret(p_src, p_src_len, r_hash, 0); return ret ? FAILED : OK; } + +#ifdef ECDSA_ENABLED + +#define CHECK_COND_V(m_cond, m_retval) \ + if (unlikely(m_cond)) { \ + if (silent) { \ + return m_retval; \ + } else { \ + ERR_FAIL_COND_V(m_cond, m_retval); \ + } \ + } + +CryptoCore::ECDSAContext::ECDSAContext(CryptoCore::ECDSAContext::CurveType p_curve) { + curve_type = p_curve; + entropy = memalloc(sizeof(mbedtls_entropy_context)); + mbedtls_entropy_init((mbedtls_entropy_context *)entropy); + + ctr_drbg = memalloc(sizeof(mbedtls_ctr_drbg_context)); + mbedtls_ctr_drbg_init((mbedtls_ctr_drbg_context *)ctr_drbg); + mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context *)ctr_drbg, mbedtls_entropy_func, (mbedtls_entropy_context *)entropy, nullptr, 0); + + ctx = memalloc(sizeof(mbedtls_ecdsa_context)); + mbedtls_ecdsa_init((mbedtls_ecdsa_context *)ctx); + + keypair = memalloc(sizeof(mbedtls_ecp_keypair)); + mbedtls_ecp_keypair_init((mbedtls_ecp_keypair *)keypair); +} + +CryptoCore::ECDSAContext::~ECDSAContext() { + mbedtls_ecp_keypair_free((mbedtls_ecp_keypair *)keypair); + mbedtls_ecdsa_free((mbedtls_ecdsa_context *)ctx); + mbedtls_entropy_free((mbedtls_entropy_context *)entropy); + mbedtls_ctr_drbg_free((mbedtls_ctr_drbg_context *)ctr_drbg); +} + +void CryptoCore::ECDSAContext::set_silent(bool p_silent) { + silent = p_silent; +} + +Error CryptoCore::ECDSAContext::validate_private_key(const uint8_t *p_priv_key, size_t p_priv_len) { + mbedtls_ecp_keypair keypair_val; + mbedtls_ecp_keypair_init(&keypair_val); + + int priv_ok = mbedtls_ecp_read_key((mbedtls_ecp_group_id)curve_type, &keypair_val, (const unsigned char *)p_priv_key, p_priv_len); + if (priv_ok == 0) { + priv_ok = mbedtls_ecp_check_privkey(&(keypair_val.grp), &(keypair_val.d)); + } + mbedtls_ecp_keypair_free(&keypair_val); + + return (priv_ok == 0) ? OK : FAILED; +} + +Error CryptoCore::ECDSAContext::validate_public_key(const uint8_t *p_pub_key, size_t p_pub_len) { + mbedtls_ecp_keypair keypair_val; + mbedtls_ecp_keypair_init(&keypair_val); + mbedtls_ecp_group_load(&(keypair_val.grp), (mbedtls_ecp_group_id)curve_type); + + int pub_ok = mbedtls_ecp_point_read_binary(&(keypair_val.grp), &(keypair_val.Q), (const unsigned char *)p_pub_key, p_pub_len); + if (pub_ok == 0) { + pub_ok = mbedtls_ecp_check_pubkey(&(keypair_val.grp), &(keypair_val.Q)); + } + mbedtls_ecp_keypair_free(&keypair_val); + + return (pub_ok == 0) ? OK : FAILED; +} + +Error CryptoCore::ECDSAContext::generate_key_pair(uint8_t *p_priv_key, size_t p_priv_len, size_t *r_priv_len, uint8_t *p_pub_key, size_t p_pub_len, size_t *r_pub_len) { + CHECK_COND_V(mbedtls_ecp_gen_key((mbedtls_ecp_group_id)curve_type, (mbedtls_ecp_keypair *)keypair, mbedtls_ctr_drbg_random, (mbedtls_ctr_drbg_context *)ctr_drbg) != 0, FAILED); + CHECK_COND_V(mbedtls_ecdsa_from_keypair((mbedtls_ecdsa_context *)ctx, (mbedtls_ecp_keypair *)keypair) != 0, FAILED); + size_t len = MIN(size_t((((mbedtls_ecp_keypair *)keypair)->grp.pbits + 7) / 8), p_priv_len); + CHECK_COND_V(mbedtls_ecp_write_key((mbedtls_ecp_keypair *)keypair, (unsigned char *)p_priv_key, len) != 0, FAILED); + *r_priv_len = len; + CHECK_COND_V(mbedtls_ecp_point_write_binary(&(((mbedtls_ecp_keypair *)keypair)->grp), &(((mbedtls_ecp_keypair *)keypair)->Q), MBEDTLS_ECP_PF_UNCOMPRESSED, r_pub_len, (unsigned char *)p_pub_key, p_pub_len) != 0, FAILED); + return OK; +} + +Error CryptoCore::ECDSAContext::set_public_key(const uint8_t *p_key, size_t p_len) { + mbedtls_ecp_group_load(&(((mbedtls_ecp_keypair *)keypair)->grp), (mbedtls_ecp_group_id)curve_type); + CHECK_COND_V(mbedtls_ecp_point_read_binary(&(((mbedtls_ecp_keypair *)keypair)->grp), &(((mbedtls_ecp_keypair *)keypair)->Q), (const unsigned char *)p_key, p_len) != 0, FAILED); + CHECK_COND_V(mbedtls_ecdsa_from_keypair((mbedtls_ecdsa_context *)ctx, (mbedtls_ecp_keypair *)keypair) != 0, FAILED); + return OK; +} + +Error CryptoCore::ECDSAContext::set_private_key(const uint8_t *p_key, size_t p_len) { + CHECK_COND_V(mbedtls_ecp_read_key((mbedtls_ecp_group_id)curve_type, (mbedtls_ecp_keypair *)keypair, (const unsigned char *)p_key, p_len) != 0, FAILED); + CHECK_COND_V(mbedtls_ecdsa_from_keypair((mbedtls_ecdsa_context *)ctx, (mbedtls_ecp_keypair *)keypair) != 0, FAILED); + return OK; +} + +Error CryptoCore::ECDSAContext::sign(const unsigned char *p_hash_sha256, uint8_t *r_signature, size_t *r_signature_len) { + CHECK_COND_V(mbedtls_ecdsa_write_signature((mbedtls_ecdsa_context *)ctx, MBEDTLS_MD_SHA256, p_hash_sha256, 32, r_signature, r_signature_len, mbedtls_ctr_drbg_random, (mbedtls_ctr_drbg_context *)ctr_drbg) != 0, FAILED); + return OK; +} + +Error CryptoCore::ECDSAContext::verify(const unsigned char *p_hash_sha256, uint8_t *p_signature, size_t p_signature_len) { + CHECK_COND_V(mbedtls_ecdsa_read_signature((mbedtls_ecdsa_context *)ctx, p_hash_sha256, 32, p_signature, p_signature_len) != 0, FAILED); + return OK; +} + +#undef CHECK_COND_V + +#endif //ECDSA_ENABLED diff --git a/core/crypto/crypto_core.h b/core/crypto/crypto_core.h index 4a9ffda842637a..40f75e0fc1a5d4 100644 --- a/core/crypto/crypto_core.h +++ b/core/crypto/crypto_core.h @@ -114,6 +114,55 @@ class CryptoCore { static Error md5(const uint8_t *p_src, int p_src_len, unsigned char r_hash[16]); static Error sha1(const uint8_t *p_src, int p_src_len, unsigned char r_hash[20]); static Error sha256(const uint8_t *p_src, int p_src_len, unsigned char r_hash[32]); + +#ifdef ECDSA_ENABLED + + class ECDSAContext { + public: + enum CurveType { + ECP_DP_NONE, + ECP_DP_SECP192R1, + ECP_DP_SECP224R1, + ECP_DP_SECP256R1, + ECP_DP_SECP384R1, + ECP_DP_SECP521R1, + ECP_DP_BP256R1, + ECP_DP_BP384R1, + ECP_DP_BP512R1, + ECP_DP_CURVE25519, + ECP_DP_SECP192K1, + ECP_DP_SECP224K1, + ECP_DP_SECP256K1, + ECP_DP_CURVE448, + }; + + private: + CurveType curve_type = ECP_DP_SECP256R1; + void *entropy = nullptr; + void *ctr_drbg = nullptr; + void *ctx = nullptr; + void *keypair = nullptr; + bool silent = false; + + public: + ECDSAContext(CurveType p_curve = ECP_DP_BP256R1); + ~ECDSAContext(); + + void set_silent(bool p_silent); + + Error validate_private_key(const uint8_t *p_priv_key, size_t p_priv_len); + Error validate_public_key(const uint8_t *p_pub_key, size_t p_pub_len); + + Error generate_key_pair(uint8_t *p_priv_key, size_t p_priv_len, size_t *r_priv_len, uint8_t *p_pub_key, size_t p_pub_len, size_t *r_pub_len); + + Error set_public_key(const uint8_t *p_key, size_t p_len); + Error set_private_key(const uint8_t *p_key, size_t p_len); + + Error sign(const unsigned char *p_hash_sha256, uint8_t *r_signature, size_t *r_signature_len); + Error verify(const unsigned char *p_hash_sha256, uint8_t *p_signature, size_t p_signature_len); + }; + +#endif }; #endif // CRYPTO_CORE_H diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 5a4d6dd0997a41..55906da79ed052 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -30,13 +30,17 @@ #include "file_access_pack.h" +#include "core/crypto/crypto_core.h" #include "core/io/file_access_encrypted.h" -#include "core/object/script_language.h" #include "core/os/os.h" +#include "core/script_encryption_key.gen.h" #include "core/version.h" #include +HashSet PackedData::require_verification; +HashSet PackedData::require_encryption; + Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) { for (int i = 0; i < sources.size(); i++) { if (sources[i]->try_open_pack(p_path, p_replace_files, p_offset)) { @@ -47,7 +51,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t return ERR_FILE_UNRECOGNIZED; } -void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) { +void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, const uint8_t *p_sha256, PackSource *p_src, bool p_replace_files, bool p_encrypted, bool p_require_verification) { String simplified_path = p_path.simplify_path(); PathMD5 pmd5(simplified_path.md5_buffer()); @@ -55,12 +59,22 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 PackedFile pf; pf.encrypted = p_encrypted; + pf.require_verification = p_require_verification; pf.pack = p_pkg_path; pf.offset = p_ofs; pf.size = p_size; for (int i = 0; i < 16; i++) { pf.md5[i] = p_md5[i]; } + if (p_sha256) { + for (int i = 0; i < 32; i++) { + pf.sha256[i] = p_sha256[i]; + } + } else { + for (int i = 0; i < 32; i++) { + pf.sha256[i] = 0; + } + } pf.src = p_src; if (!exists || p_replace_files) { @@ -118,6 +132,28 @@ void PackedData::_free_packed_dirs(PackedDir *p_dir) { memdelete(p_dir); } +bool PackedData::file_require_verification(const String &p_name) { + if (require_verification.is_empty()) { + // Core files, always encrypt if PCK signing is enabled. + require_verification.insert("project.godot"); + require_verification.insert("project.binary"); + require_verification.insert("extension_list.cfg"); + require_verification.insert("override.cfg"); + } + return require_verification.has(p_name.get_file()); +} + +bool PackedData::file_require_encryption(const String &p_name) { + if (require_encryption.is_empty()) { + // Core files, always encrypt if PCK encryption is enabled. + require_encryption.insert("project.godot"); + require_encryption.insert("project.binary"); + require_encryption.insert("extension_list.cfg"); + require_encryption.insert("override.cfg"); + } + return require_encryption.has(p_name.get_file()); +} + PackedData::~PackedData() { for (int i = 0; i < sources.size(); i++) { memdelete(sources[i]); @@ -201,7 +237,11 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint32_t ver_minor = f->get_32(); f->get_32(); // patch number, not used for validation. +#ifndef DISABLE_DEPRECATED + ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION && version != PACK_FORMAT_VERSION_COMPAT, false, "Pack version unsupported: " + itos(version) + "."); +#else ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + "."); +#endif ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + "."); uint32_t pack_flags = f->get_32(); @@ -209,13 +249,33 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED); - for (int i = 0; i < 16; i++) { +#if defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + uint64_t signature_offset = f->get_64(); + uint64_t signature_size = f->get_64(); + uint32_t signature_curve = f->get_32(); +#else + f->get_64(); + f->get_64(); + f->get_32(); +#endif // defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + + for (int i = 0; i < 11; i++) { //reserved f->get_32(); } int file_count = f->get_32(); +#if defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + // Read signature. + uint64_t dir_start_pos = f->get_position(); + Vector signature; + signature.resize(signature_size); + f->seek(signature_offset); + f->get_buffer(signature.ptrw(), signature_size); + f->seek(dir_start_pos); +#endif // defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + if (enc_directory) { Ref fae; fae.instantiate(); @@ -224,7 +284,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, Vector key; key.resize(32); for (int i = 0; i < key.size(); i++) { - key.write[i] = script_encryption_key[i]; + key.write[i] = pck_encryption_key[i]; } Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false); @@ -232,30 +292,90 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, f = fae; } +#if defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + // Compiled with embedded public key, enforce PCK signature validation. + ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + "."); + ERR_FAIL_COND_V_MSG(signature_offset == 0 || signature_size == 0, false, "Pack directory integrity verification failed."); + + // Compute directory hash. + Vector directory_hash; + directory_hash.resize(32); + CryptoCore::SHA256Context sha_ctx; + sha_ctx.start(); + dir_start_pos = f->get_position(); for (int i = 0; i < file_count; i++) { - uint32_t sl = f->get_32(); - CharString cs; - cs.resize(sl + 1); - f->get_buffer((uint8_t *)cs.ptr(), sl); - cs[sl] = 0; + uint32_t sz = 4 + f->get_32() + 8 + 8 + 16 + 32 + 4; + f->seek(f->get_position() - 4); + + Vector dir_record; + dir_record.resize(sz); + f->get_buffer(dir_record.ptrw(), sz); + sha_ctx.update((const unsigned char *)dir_record.ptr(), sz); + } + sha_ctx.finish((unsigned char *)directory_hash.ptrw()); + + CryptoCore::ECDSAContext ecdsa_ctx((CryptoCore::ECDSAContext::CurveType)signature_curve); + ecdsa_ctx.set_silent(true); + ecdsa_ctx.set_public_key(&pck_sign_pub_key[0], pck_sign_pub_key_len); + ERR_FAIL_COND_V_MSG(ecdsa_ctx.verify((const unsigned char *)directory_hash.ptr(), (unsigned char *)signature.ptr(), signature_size) != OK, false, "Pack directory integrity verification failed."); + + f->seek(dir_start_pos); +#endif // defined(ECDSA_ENABLED) && defined(PCK_SIGNING_ENABLED) + + // Read directory. + if (version == PACK_FORMAT_VERSION) { + for (int i = 0; i < file_count; i++) { + uint32_t sl = f->get_32(); + CharString cs; + cs.resize(sl + 1); + f->get_buffer((uint8_t *)cs.ptr(), sl); + cs[sl] = 0; + + String path; + path.parse_utf8(cs.ptr()); + + uint64_t ofs = file_base + f->get_64(); + uint64_t size = f->get_64(); + uint8_t md5[16]; + f->get_buffer(md5, 16); - String path; - path.parse_utf8(cs.ptr()); + uint8_t sha256[32]; + f->get_buffer(sha256, 32); - uint64_t ofs = file_base + f->get_64(); - uint64_t size = f->get_64(); - uint8_t md5[16]; - f->get_buffer(md5, 16); - uint32_t flags = f->get_32(); + uint32_t flags = f->get_32(); - PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED)); + PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, sha256, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), (flags & PACK_FILE_REQUIRE_VERIFICATION)); + } + } + +#ifndef DISABLE_DEPRECATED + if (version == PACK_FORMAT_VERSION_COMPAT) { + for (int i = 0; i < file_count; i++) { + uint32_t sl = f->get_32(); + CharString cs; + cs.resize(sl + 1); + f->get_buffer((uint8_t *)cs.ptr(), sl); + cs[sl] = 0; + + String path; + path.parse_utf8(cs.ptr()); + + uint64_t ofs = file_base + f->get_64(); + uint64_t size = f->get_64(); + uint8_t md5[16]; + f->get_buffer(md5, 16); + uint32_t flags = f->get_32(); + + PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, nullptr, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), false); + } } +#endif return true; } Ref PackedSourcePCK::get_file(const String &p_path, PackedData::PackedFile *p_file) { - return memnew(FileAccessPack(p_path, *p_file)); + return memnew(FileAccessPack(p_path, p_file)); } ////////////////////////////////////////////////////////////////// @@ -371,10 +491,10 @@ void FileAccessPack::close() { f = Ref(); } -FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) : - pf(p_file), +FileAccessPack::FileAccessPack(const String &p_path, PackedData::PackedFile *p_file) : + pf(*p_file), f(FileAccess::open(pf.pack, FileAccess::READ)) { - ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(pf.pack) + "'."); + ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(p_path) + "', pack '" + String(pf.pack) + "'."); f->seek(pf.offset); off = pf.offset; @@ -382,21 +502,56 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil if (pf.encrypted) { Ref fae; fae.instantiate(); - ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'."); + ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(p_path) + "', pack '" + String(pf.pack) + "'."); Vector key; key.resize(32); for (int i = 0; i < key.size(); i++) { - key.write[i] = script_encryption_key[i]; + key.write[i] = pck_encryption_key[i]; } Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false); - ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'."); + ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(p_path) + "', pack '" + String(pf.pack) + "'."); f = fae; off = 0; } pos = 0; eof = false; + + if (pf.require_verification && !pf.is_validated) { + Vector file_hash; + file_hash.resize(32); + + CryptoCore::SHA256Context sha_ctx; + sha_ctx.start(); + + unsigned char step[4096]; + while (true) { + uint64_t br = get_buffer(step, 4096); + if (br > 0) { + sha_ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + sha_ctx.finish((unsigned char *)file_hash.ptrw()); + + for (int i = 0; i < 32; i++) { + if (file_hash[i] != pf.sha256[i]) { + f = Ref(); + eof = true; + ERR_FAIL_MSG("Can't open encrypted pack-referenced file '" + String(p_path) + "', pack '" + String(pf.pack) + "'."); + } + } + + p_file->is_validated = true; // Only check hash once. + + f->seek(off); + } + pos = 0; + eof = false; } ////////////////////////////////////////////////////////////////////////////////// diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 97391a56110821..58d9c0edc09385 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -40,15 +40,21 @@ // Godot's packed file magic header ("GDPC" in ASCII). #define PACK_HEADER_MAGIC 0x43504447 + // The current packed file format version number. -#define PACK_FORMAT_VERSION 2 +#define PACK_FORMAT_VERSION 3 + +#ifndef DISABLE_DEPRECATED +#define PACK_FORMAT_VERSION_COMPAT 2 +#endif enum PackFlags { - PACK_DIR_ENCRYPTED = 1 << 0 + PACK_DIR_ENCRYPTED = 1 << 0, }; enum PackFileFlags { - PACK_FILE_ENCRYPTED = 1 << 0 + PACK_FILE_ENCRYPTED = 1 << 0, + PACK_FILE_REQUIRE_VERIFICATION = 1 << 1, }; class PackSource; @@ -61,11 +67,15 @@ class PackedData { public: struct PackedFile { String pack; - uint64_t offset; //if offset is ZERO, the file was ERASED + uint64_t offset; // If offset is ZERO, the file was ERASED. uint64_t size; - uint8_t md5[16]; + uint8_t md5[16]; // Used for as path name. + uint8_t sha256[32]; // Used for context verification. + PackSource *src = nullptr; - bool encrypted; + bool encrypted = false; + bool require_verification = false; + bool is_validated = false; }; private: @@ -96,6 +106,8 @@ class PackedData { } }; + static HashSet require_verification; + static HashSet require_encryption; HashMap files; Vector sources; @@ -108,8 +120,11 @@ class PackedData { void _free_packed_dirs(PackedDir *p_dir); public: + static bool file_require_verification(const String &p_name); + static bool file_require_encryption(const String &p_name); + void add_pack_source(PackSource *p_source); - void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource + void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, const uint8_t *p_sha256, PackSource *p_src, bool p_replace_files, bool p_encrypted = false, bool p_require_verification = false); // for PackSource void set_disabled(bool p_disabled) { disabled = p_disabled; } _FORCE_INLINE_ bool is_disabled() const { return disabled; } @@ -185,7 +200,7 @@ class FileAccessPack : public FileAccess { virtual void close() override; - FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file); + FileAccessPack(const String &p_path, PackedData::PackedFile *p_file); }; Ref PackedData::try_open_path(const String &p_path) { diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index dd4533241227f3..b780b4b8e5694b 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -195,7 +195,7 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint6 files[fname] = f; uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, this, p_replace_files, false); + PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, nullptr, this, p_replace_files, false, false); //printf("packed data add path %s, %s\n", p_name.utf8().get_data(), fname.utf8().get_data()); if ((i + 1) < gi.number_entry) { diff --git a/core/io/pck_packer.compat.inc b/core/io/pck_packer.compat.inc new file mode 100644 index 00000000000000..884d64317ce900 --- /dev/null +++ b/core/io/pck_packer.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* pck_packer.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +Error PCKPacker::_add_file_bind_compat_87696(const String &p_file, const String &p_src, bool p_encrypt) { + return add_file(p_file, p_src, p_encrypt, false); +} + +void PCKPacker::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::_add_file_bind_compat_87696, DEFVAL(false)); +} + +#endif diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index a7c715c3189290..9c68c6a22d85b5 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "pck_packer.h" +#include "pck_packer.compat.inc" #include "core/crypto/crypto_core.h" #include "core/io/file_access.h" @@ -48,8 +49,9 @@ static int _get_pad(int p_alignment, int p_n) { void PCKPacker::_bind_methods() { ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt", "require_verification"), &PCKPacker::add_file, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("flush_and_sign", "private_key", "curve", "verbose"), &PCKPacker::flush_and_sign, DEFVAL(3), DEFVAL(false)); } Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) { @@ -106,7 +108,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & return OK; } -Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) { +Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt, bool require_verification) { ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use."); Ref f = FileAccess::open(p_src, FileAccess::READ); @@ -124,14 +126,14 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr Vector data = FileAccess::get_file_as_bytes(p_src); { - unsigned char hash[16]; - CryptoCore::md5(data.ptr(), data.size(), hash); pf.md5.resize(16); - for (int i = 0; i < 16; i++) { - pf.md5.write[i] = hash[i]; - } + CryptoCore::md5(data.ptr(), data.size(), pf.md5.ptrw()); + + pf.sha256.resize(32); + CryptoCore::sha256(data.ptr(), data.size(), pf.sha256.ptrw()); } pf.encrypted = p_encrypt; + pf.require_verification = require_verification; uint64_t _size = pf.size; if (p_encrypt) { // Add encryption overhead. @@ -152,12 +154,27 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr } Error PCKPacker::flush(bool p_verbose) { + return flush_and_sign(String(), p_verbose); +} + +Error PCKPacker::flush_and_sign(const String &p_private_key, int p_curve, bool p_verbose) { ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use."); +#if !defined(ECDSA_ENABLED) + ERR_FAIL_COND_V_MSG(!p_private_key.is_empty(), ERR_INVALID_PARAMETER, "Engine was built without ECDSA support."); +#endif + int64_t file_base_ofs = file->get_position(); file->store_64(0); // files base - for (int i = 0; i < 16; i++) { +#if defined(ECDSA_ENABLED) + uint64_t signature_info_ofs = file->get_position(); +#endif + file->store_64(0); // signature ofs + file->store_64(0); // signature size + file->store_32(0); // signature curve + + for (int i = 0; i < 11; i++) { file->store_32(0); // reserved } @@ -177,26 +194,66 @@ Error PCKPacker::flush(bool p_verbose) { fhead = fae; } + Vector directory_hash; + directory_hash.resize(32); + CryptoCore::SHA256Context sha_ctx; + sha_ctx.start(); + for (int i = 0; i < files.size(); i++) { + static uint8_t zero = 0; int string_len = files[i].path.utf8().length(); int pad = _get_pad(4, string_len); - fhead->store_32(string_len + pad); + uint32_t full_len = string_len + pad; + fhead->store_32(full_len); + sha_ctx.update((const unsigned char *)&full_len, 4); fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len); + sha_ctx.update((const unsigned char *)files[i].path.utf8().get_data(), string_len); for (int j = 0; j < pad; j++) { fhead->store_8(0); + sha_ctx.update((const unsigned char *)&zero, 1); } fhead->store_64(files[i].ofs); + sha_ctx.update((const unsigned char *)&files[i].ofs, 8); fhead->store_64(files[i].size); // pay attention here, this is where file is + sha_ctx.update((const unsigned char *)&files[i].size, 8); fhead->store_buffer(files[i].md5.ptr(), 16); //also save md5 for file + sha_ctx.update((const unsigned char *)files[i].md5.ptr(), 16); + fhead->store_buffer(files[i].sha256.ptr(), 32); //also save sha256 for file + sha_ctx.update((const unsigned char *)files[i].sha256.ptr(), 32); uint32_t flags = 0; if (files[i].encrypted) { flags |= PACK_FILE_ENCRYPTED; } + if (files[i].require_verification) { + flags |= PACK_FILE_REQUIRE_VERIFICATION; + } fhead->store_32(flags); + sha_ctx.update((const unsigned char *)&flags, 4); + } + sha_ctx.finish((unsigned char *)directory_hash.ptrw()); + +#if defined(ECDSA_ENABLED) + Vector signature; + size_t signature_size = 4096; + if (!p_private_key.is_empty()) { + CharString priv_key = p_private_key.ascii(); + + size_t priv_key_len = 0; + Vector buf_priv; + buf_priv.resize(1024); + uint8_t *w = buf_priv.ptrw(); + ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf_priv.size(), &priv_key_len, (unsigned char *)priv_key.ptr(), priv_key.length()) != OK, ERR_CANT_CREATE); + + signature.resize(signature_size); + CryptoCore::ECDSAContext ecdsa_ctx((CryptoCore::ECDSAContext::CurveType)p_curve); + ecdsa_ctx.set_private_key(buf_priv.ptr(), priv_key_len); + ERR_FAIL_COND_V_MSG(ecdsa_ctx.sign((const unsigned char *)directory_hash.ptr(), (unsigned char *)signature.ptr(), &signature_size) != OK, ERR_CANT_CREATE, "Pack directory signing failed."); + signature.resize(signature_size); } +#endif if (fae.is_valid()) { fhead.unref(); @@ -254,6 +311,25 @@ Error PCKPacker::flush(bool p_verbose) { } } +#if defined(ECDSA_ENABLED) + if (!p_private_key.is_empty()) { + int signature_padding = _get_pad(alignment, file->get_position()); + for (int i = 0; i < signature_padding; i++) { + file->store_8(0); + } + uint64_t signature_offset = file->get_position(); + file->store_buffer(signature); + uint64_t signature_end = file->get_position(); + + // Update signature offset and size. + file->seek(signature_info_ofs); + file->store_64(signature_offset); + file->store_64(signature_size); + file->store_32(p_curve); + file->seek(signature_end); + } +#endif + file.unref(); memdelete_arr(buf); diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h index 8764fc90a01502..d698b5ecd97b2c 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -53,14 +53,23 @@ class PCKPacker : public RefCounted { uint64_t ofs = 0; uint64_t size = 0; bool encrypted = false; + bool require_verification = false; Vector md5; + Vector sha256; }; Vector files; +protected: +#ifndef DISABLE_DEPRECATED + Error _add_file_bind_compat_87696(const String &p_file, const String &p_src, bool p_encrypt); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + public: Error pck_start(const String &p_file, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); - Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false); + Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false, bool require_verification = false); Error flush(bool p_verbose = false); + Error flush_and_sign(const String &p_private_key, int p_curve, bool p_verbose = false); PCKPacker() {} }; diff --git a/core/object/script_language.h b/core/object/script_language.h index bb714d5bc35756..869e8d35a8b92d 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -403,8 +403,6 @@ class ScriptLanguage : public Object { virtual ~ScriptLanguage() {} }; -extern uint8_t script_encryption_key[32]; - class PlaceHolderScriptInstance : public ScriptInstance { Object *owner = nullptr; List properties; diff --git a/doc/classes/PCKPacker.xml b/doc/classes/PCKPacker.xml index 2627c8b7d3ea4f..7bcc1f316acaa4 100644 --- a/doc/classes/PCKPacker.xml +++ b/doc/classes/PCKPacker.xml @@ -29,6 +29,7 @@ + Adds the [param source_path] file to the current PCK package at the [param pck_path] internal path (should start with [code]res://[/code]). @@ -37,7 +38,16 @@ - Writes the files specified using all [method add_file] calls since the last flush. If [param verbose] is [code]true[/code], a list of files added will be printed to the console for easier debugging. + Writes the files specified using all [method add_file] calls. If [param verbose] is [code]true[/code], a list of files added will be printed to the console for easier debugging. + + + + + + + + + Writes the files specified using all [method add_file] calls and sign PCK using [param private_key]. If [param verbose] is [code]true[/code], a list of files added will be printed to the console for easier debugging. diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 670fd0a06d94da..a39685541be045 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -85,7 +85,13 @@ void EditorExport::_save() { config->set_value(section, "encryption_exclude_filters", preset->get_enc_ex_filter()); config->set_value(section, "encrypt_pck", preset->get_enc_pck()); config->set_value(section, "encrypt_directory", preset->get_enc_directory()); - credentials->set_value(section, "script_encryption_key", preset->get_script_encryption_key()); + config->set_value(section, "signing_include_filters", preset->get_sign_in_filter()); + config->set_value(section, "signing_exclude_filters", preset->get_sign_ex_filter()); + config->set_value(section, "sign_pck", preset->get_sign_pck()); + config->set_value(section, "pck_signing_curve", preset->get_pck_signing_curve()); + credentials->set_value(section, "pck_encryption_key", preset->get_pck_encryption_key()); + credentials->set_value(section, "pck_signing_key_priv", preset->get_pck_signing_key_priv()); + credentials->set_value(section, "pck_signing_key_pub", preset->get_pck_signing_key_pub()); String option_section = "preset." + itos(i) + ".options"; @@ -282,8 +288,26 @@ void EditorExport::load_config() { if (config->has_section_key(section, "encryption_exclude_filters")) { preset->set_enc_ex_filter(config->get_value(section, "encryption_exclude_filters")); } - if (credentials->has_section_key(section, "script_encryption_key")) { - preset->set_script_encryption_key(credentials->get_value(section, "script_encryption_key")); + if (config->has_section_key(section, "sign_pck")) { + preset->set_sign_pck(config->get_value(section, "sign_pck")); + } + if (config->has_section_key(section, "signing_include_filters")) { + preset->set_sign_in_filter(config->get_value(section, "signing_include_filters")); + } + if (config->has_section_key(section, "signing_exclude_filters")) { + preset->set_sign_ex_filter(config->get_value(section, "signing_exclude_filters")); + } + if (config->has_section_key(section, "pck_signing_curve")) { + preset->set_pck_signing_curve(config->get_value(section, "pck_signing_curve")); + } + if (credentials->has_section_key(section, "pck_encryption_key")) { + preset->set_pck_encryption_key(credentials->get_value(section, "pck_encryption_key")); + } + if (credentials->has_section_key(section, "pck_signing_key_priv")) { + preset->set_pck_signing_key_priv(credentials->get_value(section, "pck_signing_key_priv")); + } + if (credentials->has_section_key(section, "pck_signing_key_pub")) { + preset->set_pck_signing_key_pub(credentials->get_value(section, "pck_signing_key_pub")); } String option_section = "preset." + itos(index) + ".options"; diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 440089022bd7d3..9f45cde5d1ff19 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -219,7 +219,7 @@ void EditorExportPlatform::gen_debug_flags(Vector &r_flags, int p_flags) } } -Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key) { +Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); PackData *pd = (PackData *)p_userdata; @@ -230,17 +230,43 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa sd.size = p_data.size(); sd.encrypted = false; - for (int i = 0; i < p_enc_in_filters.size(); ++i) { - if (p_path.matchn(p_enc_in_filters[i]) || p_path.replace("res://", "").matchn(p_enc_in_filters[i])) { + if (!p_key.is_empty()) { + if (PackedData::file_require_encryption(p_path)) { sd.encrypted = true; - break; + } else { + for (int i = 0; i < p_enc_in_filters.size(); ++i) { + if (p_path.matchn(p_enc_in_filters[i]) || p_path.replace("res://", "").matchn(p_enc_in_filters[i]) || p_source_path.matchn(p_enc_in_filters[i]) || p_source_path.replace("res://", "").matchn(p_enc_in_filters[i])) { + sd.encrypted = true; + break; + } + } + + for (int i = 0; i < p_enc_ex_filters.size(); ++i) { + if (p_path.matchn(p_enc_ex_filters[i]) || p_path.replace("res://", "").matchn(p_enc_ex_filters[i])) { + sd.encrypted = false; + break; + } + } } } - for (int i = 0; i < p_enc_ex_filters.size(); ++i) { - if (p_path.matchn(p_enc_ex_filters[i]) || p_path.replace("res://", "").matchn(p_enc_ex_filters[i])) { - sd.encrypted = false; - break; + if (p_is_signed) { + if (PackedData::file_require_verification(p_path)) { + sd.require_verification = true; + } else { + for (int i = 0; i < p_sign_in_filters.size(); ++i) { + if (p_path.matchn(p_sign_in_filters[i]) || p_path.replace("res://", "").matchn(p_sign_in_filters[i]) || p_source_path.matchn(p_sign_in_filters[i]) || p_source_path.replace("res://", "").matchn(p_sign_in_filters[i])) { + sd.require_verification = true; + break; + } + } + + for (int i = 0; i < p_sign_ex_filters.size(); ++i) { + if (p_path.matchn(p_sign_ex_filters[i]) || p_path.replace("res://", "").matchn(p_sign_ex_filters[i])) { + sd.require_verification = false; + break; + } + } } } @@ -271,12 +297,11 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa // Store MD5 of original file. { - unsigned char hash[16]; - CryptoCore::md5(p_data.ptr(), p_data.size(), hash); sd.md5.resize(16); - for (int i = 0; i < 16; i++) { - sd.md5.write[i] = hash[i]; - } + CryptoCore::md5(p_data.ptr(), p_data.size(), sd.md5.ptrw()); + + sd.sha256.resize(32); + CryptoCore::sha256(p_data.ptr(), p_data.size(), sd.sha256.ptrw()); } pd->file_ofs.push_back(sd); @@ -289,7 +314,7 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa return OK; } -Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key) { +Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); String path = p_path.replace_first("res://", ""); @@ -854,12 +879,42 @@ String EditorExportPlatform::_export_customize(const String &p_path, LocalVector return save_path.is_empty() ? p_path : save_path; } -String EditorExportPlatform::_get_script_encryption_key(const Ref &p_preset) const { - const String from_env = OS::get_singleton()->get_environment(ENV_SCRIPT_ENCRYPTION_KEY); +String EditorExportPlatform::_get_pck_encryption_key(const Ref &p_preset) const { +#ifndef DISABLE_DEPRECATED + const String from_env_compat = OS::get_singleton()->get_environment(ENV_SCRIPT_ENCRYPTION_KEY); + if (!from_env_compat.is_empty()) { + return from_env_compat.to_lower(); + } +#endif + const String from_env = OS::get_singleton()->get_environment(ENV_PCK_ENCRYPTION_KEY); if (!from_env.is_empty()) { return from_env.to_lower(); } - return p_preset->get_script_encryption_key().to_lower(); + return p_preset->get_pck_encryption_key().to_lower(); +} + +String EditorExportPlatform::_get_pck_signing_key_priv(const Ref &p_preset) const { + const String from_env = OS::get_singleton()->get_environment(ENV_PCK_SIGNING_KEY_PRIVATE); + if (!from_env.is_empty()) { + return from_env; + } + return p_preset->get_pck_signing_key_priv(); +} + +String EditorExportPlatform::_get_pck_signing_key_pub(const Ref &p_preset) const { + const String from_env = OS::get_singleton()->get_environment(ENV_PCK_SIGNING_KEY_PUBLIC); + if (!from_env.is_empty()) { + return from_env; + } + return p_preset->get_pck_signing_key_pub(); +} + +int EditorExportPlatform::_get_pck_signing_curve(const Ref &p_preset) const { + const String from_env = OS::get_singleton()->get_environment(ENV_PCK_SIGNING_CURVE); + if (!from_env.is_empty()) { + return from_env.to_int(); + } + return p_preset->get_pck_signing_curve(); } Vector EditorExportPlatform::get_forced_export_files() { @@ -970,8 +1025,11 @@ Error EditorExportPlatform::export_project_files(const Ref & // Get encryption filters. bool enc_pck = p_preset->get_enc_pck(); + bool sign_pck = p_preset->get_sign_pck(); Vector enc_in_filters; Vector enc_ex_filters; + Vector sign_in_filters; + Vector sign_ex_filters; Vector key; if (enc_pck) { @@ -994,13 +1052,13 @@ Error EditorExportPlatform::export_project_files(const Ref & } // Get encryption key. - String script_key = _get_script_encryption_key(p_preset); + String pck_key = _get_pck_encryption_key(p_preset); key.resize(32); - if (script_key.length() == 64) { + if (pck_key.length() == 64) { for (int i = 0; i < 32; i++) { int v = 0; - if (i * 2 < script_key.length()) { - char32_t ct = script_key[i * 2]; + if (i * 2 < pck_key.length()) { + char32_t ct = pck_key[i * 2]; if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { @@ -1009,8 +1067,8 @@ Error EditorExportPlatform::export_project_files(const Ref & v |= ct << 4; } - if (i * 2 + 1 < script_key.length()) { - char32_t ct = script_key[i * 2 + 1]; + if (i * 2 + 1 < pck_key.length()) { + char32_t ct = pck_key[i * 2 + 1]; if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { @@ -1023,6 +1081,26 @@ Error EditorExportPlatform::export_project_files(const Ref & } } + if (sign_pck) { + Vector sign_in_split = p_preset->get_sign_in_filter().split(","); + for (int i = 0; i < sign_in_split.size(); i++) { + String f = sign_in_split[i].strip_edges(); + if (f.is_empty()) { + continue; + } + sign_in_filters.push_back(f); + } + + Vector sign_ex_split = p_preset->get_sign_ex_filter().split(","); + for (int i = 0; i < sign_ex_split.size(); i++) { + String f = sign_ex_split[i].strip_edges(); + if (f.is_empty()) { + continue; + } + sign_ex_filters.push_back(f); + } + } + Error err = OK; Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); @@ -1045,7 +1123,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1162,13 +1240,13 @@ Error EditorExportPlatform::export_project_files(const Ref & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } // Now actual remapped file: sarr = FileAccess::get_file_as_bytes(export_path); - err = p_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1187,7 +1265,7 @@ Error EditorExportPlatform::export_project_files(const Ref & if (importer_type == "keep") { // Just keep file as-is. Vector array = FileAccess::get_file_as_bytes(path); - err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, path, array, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; @@ -1220,14 +1298,14 @@ Error EditorExportPlatform::export_project_files(const Ref & if (remap == "path") { String remapped_path = config->get_value("remap", remap); Vector array = FileAccess::get_file_as_bytes(remapped_path); - err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); } else if (remap.begins_with("path.")) { String feature = remap.get_slice(".", 1); if (remap_features.has(feature)) { String remapped_path = config->get_value("remap", remap); Vector array = FileAccess::get_file_as_bytes(remapped_path); - err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); } else { // Remove paths if feature not enabled. config->erase_section_key("remap", remap); @@ -1249,7 +1327,7 @@ Error EditorExportPlatform::export_project_files(const Ref & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; @@ -1276,7 +1354,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1309,7 +1387,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } Vector array = FileAccess::get_file_as_bytes(export_path); - err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, path, export_path, array, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1376,7 +1454,7 @@ Error EditorExportPlatform::export_project_files(const Ref & new_file.write[j] = utf8[j]; } - err = p_func(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, from, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1390,7 +1468,7 @@ Error EditorExportPlatform::export_project_files(const Ref & Vector forced_export = get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { Vector array = FileAccess::get_file_as_bytes(forced_export[i]); - err = p_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_func(p_udata, forced_export[i], forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); if (err != OK) { return err; } @@ -1402,7 +1480,7 @@ Error EditorExportPlatform::export_project_files(const Ref & Vector data = FileAccess::get_file_as_bytes(engine_cfb); DirAccess::remove_file_or_error(engine_cfb); - return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); + return p_func(p_udata, "res://" + config_file, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, sign_in_filters, sign_ex_filters, key, sign_pck); } Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) { @@ -1606,6 +1684,7 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b f->store_32(VERSION_PATCH); uint32_t pack_flags = 0; + bool sign_pack = p_preset->get_sign_pck(); bool enc_pck = p_preset->get_enc_pck(); bool enc_directory = p_preset->get_enc_directory(); if (enc_pck && enc_directory) { @@ -1616,7 +1695,12 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b uint64_t file_base_ofs = f->get_position(); f->store_64(0); // files base - for (int i = 0; i < 16; i++) { + uint64_t signature_info_ofs = f->get_position(); + f->store_64(0); // signature ofs + f->store_64(0); // signature size + f->store_32(0); // signature curve + + for (int i = 0; i < 11; i++) { //reserved f->store_32(0); } @@ -1627,14 +1711,14 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b Ref fhead = f; if (enc_pck && enc_directory) { - String script_key = _get_script_encryption_key(p_preset); + String pck_key = _get_pck_encryption_key(p_preset); Vector key; key.resize(32); - if (script_key.length() == 64) { + if (pck_key.length() == 64) { for (int i = 0; i < 32; i++) { int v = 0; - if (i * 2 < script_key.length()) { - char32_t ct = script_key[i * 2]; + if (i * 2 < pck_key.length()) { + char32_t ct = pck_key[i * 2]; if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { @@ -1643,8 +1727,8 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b v |= ct << 4; } - if (i * 2 + 1 < script_key.length()) { - char32_t ct = script_key[i * 2 + 1]; + if (i * 2 + 1 < pck_key.length()) { + char32_t ct = pck_key[i * 2 + 1]; if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { @@ -1670,24 +1754,69 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b fhead = fae; } + Vector directory_hash; + directory_hash.resize(32); + CryptoCore::SHA256Context sha_ctx; + sha_ctx.start(); + + print_verbose("Expoted PCK content:"); + for (int i = 0; i < pd.file_ofs.size(); i++) { + static uint8_t zero = 0; uint32_t string_len = pd.file_ofs[i].path_utf8.length(); uint32_t pad = _get_pad(4, string_len); - fhead->store_32(string_len + pad); + print_verbose(vformat(" %s %d [%s%s]", String::utf8(pd.file_ofs[i].path_utf8.get_data()), pd.file_ofs[i].size, (pd.file_ofs[i].encrypted ? "E" : ""), (pd.file_ofs[i].require_verification ? "S" : ""))); + + uint32_t full_len = string_len + pad; + fhead->store_32(full_len); + sha_ctx.update((const unsigned char *)&full_len, 4); + fhead->store_buffer((const uint8_t *)pd.file_ofs[i].path_utf8.get_data(), string_len); + sha_ctx.update((const unsigned char *)pd.file_ofs[i].path_utf8.get_data(), string_len); for (uint32_t j = 0; j < pad; j++) { fhead->store_8(0); + sha_ctx.update((const unsigned char *)&zero, 1); } fhead->store_64(pd.file_ofs[i].ofs); + sha_ctx.update((const unsigned char *)&pd.file_ofs[i].ofs, 8); fhead->store_64(pd.file_ofs[i].size); // pay attention here, this is where file is + sha_ctx.update((const unsigned char *)&pd.file_ofs[i].size, 8); fhead->store_buffer(pd.file_ofs[i].md5.ptr(), 16); //also save md5 for file + sha_ctx.update((const unsigned char *)pd.file_ofs[i].md5.ptr(), 16); + fhead->store_buffer(pd.file_ofs[i].sha256.ptr(), 32); //also save sha256 for file + sha_ctx.update((const unsigned char *)pd.file_ofs[i].sha256.ptr(), 32); uint32_t flags = 0; if (pd.file_ofs[i].encrypted) { flags |= PACK_FILE_ENCRYPTED; } + if (pd.file_ofs[i].require_verification) { + flags |= PACK_FILE_REQUIRE_VERIFICATION; + } fhead->store_32(flags); + sha_ctx.update((const unsigned char *)&flags, 4); + } + sha_ctx.finish((unsigned char *)directory_hash.ptrw()); + + Vector signature; + size_t signature_size = 4096; + int signature_curve = 0; + if (sign_pack) { + CharString priv_key = _get_pck_signing_key_priv(p_preset).ascii(); + signature_curve = _get_pck_signing_curve(p_preset); + + size_t priv_key_len = 0; + Vector buf_priv; + buf_priv.resize(1024); + uint8_t *w = buf_priv.ptrw(); + ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf_priv.size(), &priv_key_len, (unsigned char *)priv_key.ptr(), priv_key.length()) != OK, ERR_CANT_CREATE); + + signature.resize(signature_size); + CryptoCore::ECDSAContext ecdsa_ctx((CryptoCore::ECDSAContext::CurveType)signature_curve); + ecdsa_ctx.set_private_key(buf_priv.ptr(), priv_key_len); + ERR_FAIL_COND_V_MSG(ecdsa_ctx.sign((const unsigned char *)directory_hash.ptr(), (unsigned char *)signature.ptr(), &signature_size) != OK, ERR_CANT_CREATE, "Pack directory signing failed."); + signature.resize(signature_size); } if (fae.is_valid()) { @@ -1727,6 +1856,23 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, b ftmp.unref(); // Close temp file. + if (sign_pack) { + int signature_padding = _get_pad(PCK_PADDING, f->get_position()); + for (int i = 0; i < signature_padding; i++) { + f->store_8(0); + } + uint64_t signature_offset = f->get_position(); + f->store_buffer(signature); + uint64_t signature_end = f->get_position(); + + // Update signature offset and size. + f->seek(signature_info_ofs); + f->store_64(signature_offset); + f->store_64(signature_size); + f->store_32(signature_curve); + f->seek(signature_end); + } + if (p_embed) { // Ensure embedded data ends at a 64-bit multiple uint64_t embed_end = f->get_position() - embed_pos + 12; diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 0b922cc6c8f92d..56ba9ef9dd5738 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -44,7 +44,13 @@ struct EditorProgress; class EditorExportPlugin; +#ifndef DISABLE_DEPRECATED const String ENV_SCRIPT_ENCRYPTION_KEY = "GODOT_SCRIPT_ENCRYPTION_KEY"; +#endif +const String ENV_PCK_ENCRYPTION_KEY = "GODOT_PCK_ENCRYPTION_KEY"; +const String ENV_PCK_SIGNING_KEY_PRIVATE = "GODOT_ENV_PCK_SIGNING_KEY_PRIVATE"; +const String ENV_PCK_SIGNING_KEY_PUBLIC = "GODOT_ENV_PCK_SIGNING_KEY_PUBLIC"; +const String ENV_PCK_SIGNING_CURVE = "GODOT_ENV_PCK_SIGNING_CURVE"; class EditorExportPlatform : public RefCounted { GDCLASS(EditorExportPlatform, RefCounted); @@ -53,7 +59,7 @@ class EditorExportPlatform : public RefCounted { static void _bind_methods(); public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); enum ExportMessageType { @@ -74,7 +80,9 @@ class EditorExportPlatform : public RefCounted { uint64_t ofs = 0; uint64_t size = 0; bool encrypted = false; + bool require_verification = false; Vector md5; + Vector sha256; CharString path_utf8; bool operator<(const SavedData &p_data) const { @@ -100,8 +108,8 @@ class EditorExportPlatform : public RefCounted { void _export_find_customized_resources(const Ref &p_preset, EditorFileSystemDirectory *p_dir, EditorExportPreset::FileExportMode p_mode, HashSet &p_paths); void _export_find_dependencies(const String &p_path, HashSet &p_paths); - static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); - static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); + static Error _save_pack_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); + static Error _save_zip_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); void _edit_files_with_filter(Ref &da, const Vector &p_filters, HashSet &r_list, bool exclude); void _edit_filter_list(HashSet &r_list, const String &p_filter, bool exclude); @@ -122,7 +130,10 @@ class EditorExportPlatform : public RefCounted { bool _is_editable_ancestor(Node *p_root, Node *p_node); String _export_customize(const String &p_path, LocalVector> &customize_resources_plugins, LocalVector> &customize_scenes_plugins, HashMap &export_cache, const String &export_base_path, bool p_force_save); - String _get_script_encryption_key(const Ref &p_preset) const; + String _get_pck_encryption_key(const Ref &p_preset) const; + String _get_pck_signing_key_priv(const Ref &p_preset) const; + String _get_pck_signing_key_pub(const Ref &p_preset) const; + int _get_pck_signing_curve(const Ref &p_preset) const; protected: struct ExportNotifier { diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index b941170b7bc04a..f095c2787ab7ab 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -314,15 +314,69 @@ bool EditorExportPreset::get_enc_directory() const { return enc_directory; } -void EditorExportPreset::set_script_encryption_key(const String &p_key) { +void EditorExportPreset::set_pck_encryption_key(const String &p_key) { script_key = p_key; EditorExport::singleton->save_presets(); } -String EditorExportPreset::get_script_encryption_key() const { +String EditorExportPreset::get_pck_encryption_key() const { return script_key; } +void EditorExportPreset::set_sign_in_filter(const String &p_filter) { + sign_in_filters = p_filter; + EditorExport::singleton->save_presets(); +} + +String EditorExportPreset::get_sign_in_filter() const { + return sign_in_filters; +} + +void EditorExportPreset::set_sign_ex_filter(const String &p_filter) { + sign_ex_filters = p_filter; + EditorExport::singleton->save_presets(); +} + +String EditorExportPreset::get_sign_ex_filter() const { + return sign_ex_filters; +} + +void EditorExportPreset::set_sign_pck(bool p_enabled) { + sign_pck = p_enabled; + EditorExport::singleton->save_presets(); +} + +bool EditorExportPreset::get_sign_pck() const { + return sign_pck; +} + +void EditorExportPreset::set_pck_signing_key_priv(const String &p_key) { + sign_key_priv = p_key; + EditorExport::singleton->save_presets(); +} + +String EditorExportPreset::get_pck_signing_key_priv() const { + return sign_key_priv; +} + +void EditorExportPreset::set_pck_signing_key_pub(const String &p_key) { + sign_key_pub = p_key; + EditorExport::singleton->save_presets(); +} + +String EditorExportPreset::get_pck_signing_key_pub() const { + return sign_key_pub; +} + +void EditorExportPreset::set_pck_signing_curve(int p_curve) { + sign_curve = p_curve; + EditorExport::singleton->save_presets(); +} + +int EditorExportPreset::get_pck_signing_curve() const { + return sign_curve; +} + Variant EditorExportPreset::get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid) const { const String from_env = OS::get_singleton()->get_environment(p_env_var); if (!from_env.is_empty()) { diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index 025e7603f3463a..0cf325f6fd72e8 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -78,13 +78,21 @@ class EditorExportPreset : public RefCounted { String custom_features; - String enc_in_filters; + String enc_in_filters = "*.gd"; String enc_ex_filters; bool enc_pck = false; bool enc_directory = false; String script_key; + String sign_in_filters = "*.*"; + String sign_ex_filters; + bool sign_pck = false; + + String sign_key_priv; + String sign_key_pub; + int sign_curve = 3; + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -149,8 +157,26 @@ class EditorExportPreset : public RefCounted { void set_enc_directory(bool p_enabled); bool get_enc_directory() const; - void set_script_encryption_key(const String &p_key); - String get_script_encryption_key() const; + void set_pck_encryption_key(const String &p_key); + String get_pck_encryption_key() const; + + void set_sign_in_filter(const String &p_filter); + String get_sign_in_filter() const; + + void set_sign_ex_filter(const String &p_filter); + String get_sign_ex_filter() const; + + void set_sign_pck(bool p_enabled); + bool get_sign_pck() const; + + void set_pck_signing_key_priv(const String &p_key); + String get_pck_signing_key_priv() const; + + void set_pck_signing_curve(int p_curve); + int get_pck_signing_curve() const; + + void set_pck_signing_key_pub(const String &p_key); + String get_pck_signing_key_pub() const; Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 63bd87e6cc00ce..06a273ad0c6a91 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -31,6 +31,7 @@ #include "project_export.h" #include "core/config/project_settings.h" +#include "core/crypto/crypto_core.h" #include "core/version.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" @@ -360,27 +361,62 @@ void ProjectExportDialog::_edit_preset(int p_index) { enc_directory->set_disabled(!enc_pck_mode); enc_in_filters->set_editable(enc_pck_mode); enc_ex_filters->set_editable(enc_pck_mode); - script_key->set_editable(enc_pck_mode); + pck_key->set_editable(enc_pck_mode); + pck_key_gen->set_disabled(!enc_pck_mode); bool enc_directory_mode = current->get_enc_directory(); enc_directory->set_pressed(enc_directory_mode); - String key = current->get_script_encryption_key(); - if (!updating_script_key) { - script_key->set_text(key); + String pck_key_value = current->get_pck_encryption_key(); + if (!updating_pck_key) { + pck_key->set_text(pck_key_value); } if (enc_pck_mode) { - script_key->set_editable(true); + bool key_valid = _validate_pck_encryption_key(pck_key_value); + if (key_valid) { + pck_key_error->hide(); + } else { + pck_key_error->show(); + } + } else { + pck_key_error->hide(); + } + + String sign_in_filters_str = current->get_sign_in_filter(); + String sign_ex_filters_str = current->get_sign_ex_filter(); + if (!updating_sign_filters) { + sign_in_filters->set_text(sign_in_filters_str); + sign_ex_filters->set_text(sign_ex_filters_str); + } - bool key_valid = _validate_script_encryption_key(key); + bool sign_pck_mode = current->get_sign_pck(); + sign_pck->set_pressed(sign_pck_mode); + + sign_in_filters->set_editable(sign_pck_mode); + sign_ex_filters->set_editable(sign_pck_mode); + sign_key_priv->set_editable(sign_pck_mode); + sign_key_pub->set_editable(sign_pck_mode); + sign_key_gen->set_disabled(!sign_pck_mode); + sign_curve->set_disabled(!sign_pck_mode); + + int sign_curve_id = current->get_pck_signing_curve(); + sign_curve->select(sign_curve->get_item_index(sign_curve_id)); + + String sign_key_priv_value = current->get_pck_signing_key_priv(); + String sign_key_pub_value = current->get_pck_signing_key_pub(); + if (!updating_sign_key) { + sign_key_priv->set_text(sign_key_priv_value); + sign_key_pub->set_text(sign_key_pub_value); + } + if (sign_pck_mode) { + bool key_valid = _validate_pck_sign_key(sign_key_priv_value, sign_key_pub_value, sign_curve_id); if (key_valid) { - script_key_error->hide(); + sign_key_error->hide(); } else { - script_key_error->show(); + sign_key_error->show(); } } else { - script_key->set_editable(false); - script_key_error->hide(); + sign_key_error->hide(); } updating = false; @@ -524,8 +560,24 @@ void ProjectExportDialog::_enc_filters_changed(const String &p_filters) { updating_enc_filters = false; } +void ProjectExportDialog::_sign_filters_changed(const String &p_filters) { + if (updating) { + return; + } + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_sign_in_filter(sign_in_filters->get_text()); + current->set_sign_ex_filter(sign_ex_filters->get_text()); + + updating_sign_filters = true; + _update_current_preset(); + updating_sign_filters = false; +} + void ProjectExportDialog::_open_key_help_link() { - OS::get_singleton()->shell_open(vformat("%s/contributing/development/compiling/compiling_with_script_encryption_key.html", VERSION_DOCS_URL)); + OS::get_singleton()->shell_open(vformat("%s/contributing/development/compiling/compiling_with_pck_encryption_key.html", VERSION_DOCS_URL)); } void ProjectExportDialog::_enc_pck_changed(bool p_pressed) { @@ -540,7 +592,27 @@ void ProjectExportDialog::_enc_pck_changed(bool p_pressed) { enc_directory->set_disabled(!p_pressed); enc_in_filters->set_editable(p_pressed); enc_ex_filters->set_editable(p_pressed); - script_key->set_editable(p_pressed); + pck_key->set_editable(p_pressed); + pck_key_gen->set_disabled(!p_pressed); + + _update_current_preset(); +} + +void ProjectExportDialog::_sign_pck_changed(bool p_pressed) { + if (updating) { + return; + } + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_sign_pck(p_pressed); + sign_in_filters->set_editable(p_pressed); + sign_ex_filters->set_editable(p_pressed); + sign_key_priv->set_editable(p_pressed); + sign_key_pub->set_editable(p_pressed); + sign_key_gen->set_disabled(!p_pressed); + sign_curve->set_disabled(!p_pressed); _update_current_preset(); } @@ -558,7 +630,7 @@ void ProjectExportDialog::_enc_directory_changed(bool p_pressed) { _update_current_preset(); } -void ProjectExportDialog::_script_encryption_key_changed(const String &p_key) { +void ProjectExportDialog::_pck_encryption_key_changed(const String &p_key) { if (updating) { return; } @@ -566,14 +638,14 @@ void ProjectExportDialog::_script_encryption_key_changed(const String &p_key) { Ref current = get_current_preset(); ERR_FAIL_COND(current.is_null()); - current->set_script_encryption_key(p_key); + current->set_pck_encryption_key(p_key); - updating_script_key = true; + updating_pck_key = true; _update_current_preset(); - updating_script_key = false; + updating_pck_key = false; } -bool ProjectExportDialog::_validate_script_encryption_key(const String &p_key) { +bool ProjectExportDialog::_validate_pck_encryption_key(const String &p_key) { bool is_valid = false; if (!p_key.is_empty() && p_key.is_valid_hex_number(false) && p_key.length() == 64) { @@ -582,6 +654,142 @@ bool ProjectExportDialog::_validate_script_encryption_key(const String &p_key) { return is_valid; } +void ProjectExportDialog::_pck_sign_key_priv_changed(const String &p_key) { + if (updating) { + return; + } + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_pck_signing_key_priv(p_key); + + updating_sign_key = true; + _update_current_preset(); + updating_sign_key = false; +} + +void ProjectExportDialog::_pck_sign_key_pub_changed(const String &p_key) { + if (updating) { + return; + } + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_pck_signing_key_pub(p_key); + + updating_sign_key = true; + _update_current_preset(); + updating_sign_key = false; +} + +void ProjectExportDialog::_pck_sign_curve_changed(int p_curve) { + if (updating) { + return; + } + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_pck_signing_curve(sign_curve->get_item_id(p_curve)); + + updating_sign_key = true; + _update_current_preset(); + updating_sign_key = false; +} + +bool ProjectExportDialog::_validate_pck_sign_key(const String &p_priv_key, const String &p_pub_key, int p_curve) { + bool ok = true; + String error_text; + CryptoCore::ECDSAContext ecdsa_ctx((CryptoCore::ECDSAContext::CurveType)p_curve); + { + CharString priv_key = p_priv_key.ascii(); + size_t priv_key_len = 0; + Vector buf_priv; + buf_priv.resize(1024); + uint8_t *w = buf_priv.ptrw(); + if (CryptoCore::b64_decode(&w[0], buf_priv.size(), &priv_key_len, (unsigned char *)priv_key.ptr(), priv_key.length()) == OK) { + if (ecdsa_ctx.validate_private_key((const uint8_t *)buf_priv.ptr(), priv_key_len) != OK) { + ok = false; + error_text += String::utf8("• ") + TTR("Private key, invalid key for the curve.") + "\n"; + } + } else { + ok = false; + error_text += String::utf8("• ") + TTR("Private key, invalid Base64 encoding.") + "\n"; + } + } + { + CharString pub_key = p_pub_key.ascii(); + size_t pub_key_len = 0; + Vector buf_pub; + buf_pub.resize(1024); + uint8_t *w = buf_pub.ptrw(); + if (CryptoCore::b64_decode(&w[0], buf_pub.size(), &pub_key_len, (unsigned char *)pub_key.ptr(), pub_key.length()) == OK) { + if (ecdsa_ctx.validate_public_key((const uint8_t *)buf_pub.ptr(), pub_key_len) != OK) { + ok = false; + error_text += String::utf8("• ") + TTR("Public key, invalid key for the curve.") + "\n"; + } + } else { + ok = false; + error_text += String::utf8("• ") + TTR("Public key, invalid Base64 encoding.") + "\n"; + } + } + + sign_key_error->set_text(error_text); + return ok; +} + +void ProjectExportDialog::_pck_key_gen() { + Vector key; + key.resize(32); + + CryptoCore::RandomGenerator rng; + ERR_FAIL_COND(rng.init() != OK); + ERR_FAIL_COND(rng.get_random_bytes((uint8_t *)key.ptrw(), 32) != OK); + + String str_key = String::hex_encode_buffer((uint8_t *)key.ptr(), 32); + pck_key->set_text(str_key); + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_pck_encryption_key(str_key); + + updating_pck_key = true; + _update_current_preset(); + updating_pck_key = false; +} + +void ProjectExportDialog::_sign_key_gen() { + CryptoCore::ECDSAContext ecdsa_ctx((CryptoCore::ECDSAContext::CurveType)sign_curve->get_selected_id()); + + Vector priv_key; + size_t priv_key_size = 1024; + priv_key.resize(priv_key_size); + + Vector pub_key; + size_t pub_key_size = 1024; + pub_key.resize(pub_key_size); + + ERR_FAIL_COND(ecdsa_ctx.generate_key_pair((uint8_t *)priv_key.ptrw(), priv_key_size, &priv_key_size, (uint8_t *)pub_key.ptrw(), pub_key_size, &pub_key_size) != OK); + + String str_key_priv = CryptoCore::b64_encode_str((const uint8_t *)priv_key.ptr(), priv_key_size); + String str_key_pub = CryptoCore::b64_encode_str((const uint8_t *)pub_key.ptr(), pub_key_size); + sign_key_priv->set_text(str_key_priv); + sign_key_pub->set_text(str_key_pub); + + Ref current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_pck_signing_key_priv(str_key_priv); + current->set_pck_signing_key_pub(str_key_pub); + + updating_sign_key = true; + _update_current_preset(); + updating_sign_key = false; +} + void ProjectExportDialog::_duplicate_preset() { Ref current = get_current_preset(); if (current.is_null()) { @@ -1328,50 +1536,122 @@ ProjectExportDialog::ProjectExportDialog() { feature_vb->add_margin_child(TTR("Feature List:"), custom_feature_display, true); sections->add_child(feature_vb); - // Script export parameters. + // Pack encryption parameters. - VBoxContainer *sec_vb = memnew(VBoxContainer); - sec_vb->set_name(TTR("Encryption")); + VBoxContainer *enc_vb = memnew(VBoxContainer); + enc_vb->set_name(TTR("Encryption")); enc_pck = memnew(CheckButton); enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); enc_pck->set_text(TTR("Encrypt Exported PCK")); - sec_vb->add_child(enc_pck); + enc_vb->add_child(enc_pck); enc_directory = memnew(CheckButton); enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed)); enc_directory->set_text(TTR("Encrypt Index (File Names and Info)")); - sec_vb->add_child(enc_directory); + enc_vb->add_child(enc_directory); enc_in_filters = memnew(LineEdit); enc_in_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed)); - sec_vb->add_margin_child( + enc_vb->add_margin_child( TTR("Filters to include files/folders\n(comma-separated, e.g: *.tscn, *.tres, scenes/*)"), enc_in_filters); enc_ex_filters = memnew(LineEdit); enc_ex_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed)); - sec_vb->add_margin_child( + enc_vb->add_margin_child( TTR("Filters to exclude files/folders\n(comma-separated, e.g: *.ctex, *.import, music/*)"), enc_ex_filters); - script_key = memnew(LineEdit); - script_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed)); - script_key_error = memnew(Label); - script_key_error->set_text(String::utf8("• ") + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)")); - script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor))); - sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), script_key); - sec_vb->add_child(script_key_error); - sections->add_child(sec_vb); + pck_key = memnew(LineEdit); + pck_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_pck_encryption_key_changed)); + pck_key_gen = memnew(Button); + pck_key_gen->set_text(TTR("Generate new key...")); + pck_key_gen->connect("pressed", callable_mp(this, &ProjectExportDialog::_pck_key_gen)); + pck_key_error = memnew(Label); + pck_key_error->set_text(String::utf8("• ") + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)")); + pck_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor))); + enc_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), pck_key); + enc_vb->add_child(pck_key_error); + enc_vb->add_child(pck_key_gen); Label *sec_info = memnew(Label); sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source.")); - sec_vb->add_child(sec_info); + enc_vb->add_child(sec_info); LinkButton *sec_more_info = memnew(LinkButton); sec_more_info->set_text(TTR("More Info...")); sec_more_info->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_key_help_link)); - sec_vb->add_child(sec_more_info); + enc_vb->add_child(sec_more_info); + + sections->add_child(enc_vb); + + // Pack signing parameters. + + VBoxContainer *sign_vb = memnew(VBoxContainer); + sign_vb->set_name(TTR("Signing")); + + sign_pck = memnew(CheckButton); + sign_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_sign_pck_changed)); + sign_pck->set_text(TTR("Sign Exported PCK")); + sign_vb->add_child(sign_pck); + + sign_in_filters = memnew(LineEdit); + sign_in_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_sign_filters_changed)); + sign_vb->add_margin_child( + TTR("Filters to include files/folders\n(comma-separated, e.g: *.tscn, *.tres, scenes/*)"), + sign_in_filters); + + sign_ex_filters = memnew(LineEdit); + sign_ex_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_sign_filters_changed)); + sign_vb->add_margin_child( + TTR("Filters to exclude files/folders\n(comma-separated, e.g: *.ctex, *.import, music/*)"), + sign_ex_filters); + + sign_curve = memnew(OptionButton); + sign_curve->connect("item_selected", callable_mp(this, &ProjectExportDialog::_pck_sign_curve_changed)); + sign_curve->add_item("192-bit curve defined by FIPS 186-4 and SEC1 (SECP192R1)", 1); + sign_curve->add_item("224-bit curve defined by FIPS 186-4 and SEC1 (SECP224R1)", 2); + sign_curve->add_item("256-bit curve defined by FIPS 186-4 and SEC1 (SECP256R1)", 3); + sign_curve->add_item("384-bit curve defined by FIPS 186-4 and SEC1 (SECP384R1)", 4); + sign_curve->add_item("521-bit curve defined by FIPS 186-4 and SEC1 (SECP521R1)", 5); + sign_curve->add_item("256-bit Brainpool curve (BP256R1)", 6); + sign_curve->add_item("384-bit Brainpool curve (BP384R1)", 7); + sign_curve->add_item("512-bit Brainpool curve (BP512R1)", 8); + sign_curve->add_item("Curve25519 (CURVE25519)", 9); + sign_curve->add_item("192-bit Koblitz curve (SECP192K1)", 10); + sign_curve->add_item("224-bit Koblitz curve (SECP224K1)", 11); + sign_curve->add_item("256-bit Koblitz curve (SECP256K1)", 12); + sign_curve->add_item("Curve448-Goldilocks (CURVE448)", 13); + sign_curve->select(3); + + sign_key_priv = memnew(LineEdit); + sign_key_priv->connect("text_changed", callable_mp(this, &ProjectExportDialog::_pck_sign_key_priv_changed)); + sign_key_priv->set_placeholder(TTR("Private key")); + sign_key_pub = memnew(LineEdit); + sign_key_pub->connect("text_changed", callable_mp(this, &ProjectExportDialog::_pck_sign_key_pub_changed)); + sign_key_pub->set_placeholder(TTR("Public key")); + sign_key_gen = memnew(Button); + sign_key_gen->set_text(TTR("Generate new key pair...")); + sign_key_gen->connect("pressed", callable_mp(this, &ProjectExportDialog::_sign_key_gen)); + sign_key_error = memnew(Label); + sign_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor))); + sign_vb->add_margin_child(TTR("Curve:"), sign_curve); + sign_vb->add_margin_child(TTR("Private key (Base64):"), sign_key_priv); + sign_vb->add_margin_child(TTR("Public key (Base64):"), sign_key_pub); + sign_vb->add_child(sign_key_gen); + sign_vb->add_child(sign_key_error); + + Label *sign_info = memnew(Label); + sign_info->set_text(TTR("Note: Signing public key needs to be stored in the binary,\nyou need to build the export templates from source.\nBinary compiled with the public key will refuse to load unsigned packs.")); + sign_vb->add_child(sign_info); + + LinkButton *sign_more_info = memnew(LinkButton); + sign_more_info->set_text(TTR("More Info...")); + sign_more_info->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_key_help_link)); + sign_vb->add_child(sign_more_info); + + sections->add_child(sign_vb); sections->connect("tab_changed", callable_mp(this, &ProjectExportDialog::_tab_changed)); @@ -1381,7 +1661,8 @@ ProjectExportDialog::ProjectExportDialog() { runnable->set_disabled(true); duplicate_preset->set_disabled(true); delete_preset->set_disabled(true); - script_key_error->hide(); + pck_key_error->hide(); + sign_key_error->hide(); sections->hide(); parameters->edit(nullptr); diff --git a/editor/export/project_export.h b/editor/export/project_export.h index 1a359b08dabd02..4f8d6f63d8bc10 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -107,8 +107,15 @@ class ProjectExportDialog : public ConfirmationDialog { LineEdit *custom_features = nullptr; RichTextLabel *custom_feature_display = nullptr; - LineEdit *script_key = nullptr; - Label *script_key_error = nullptr; + LineEdit *pck_key = nullptr; + Button *pck_key_gen = nullptr; + Label *pck_key_error = nullptr; + + LineEdit *sign_key_priv = nullptr; + LineEdit *sign_key_pub = nullptr; + OptionButton *sign_curve = nullptr; + Button *sign_key_gen = nullptr; + Label *sign_key_error = nullptr; ProjectExportTextureFormatError *export_texture_format_error = nullptr; Label *export_error = nullptr; @@ -160,6 +167,10 @@ class ProjectExportDialog : public ConfirmationDialog { LineEdit *enc_in_filters = nullptr; LineEdit *enc_ex_filters = nullptr; + CheckButton *sign_pck = nullptr; + LineEdit *sign_in_filters = nullptr; + LineEdit *sign_ex_filters = nullptr; + void _open_export_template_manager(); void _export_pck_zip(); @@ -175,13 +186,25 @@ class ProjectExportDialog : public ConfirmationDialog { void _update_feature_list(); void _custom_features_changed(const String &p_text); - bool updating_script_key = false; + bool updating_pck_key = false; bool updating_enc_filters = false; void _enc_pck_changed(bool p_pressed); void _enc_directory_changed(bool p_pressed); void _enc_filters_changed(const String &p_text); - void _script_encryption_key_changed(const String &p_key); - bool _validate_script_encryption_key(const String &p_key); + void _pck_encryption_key_changed(const String &p_key); + bool _validate_pck_encryption_key(const String &p_key); + + bool updating_sign_key = false; + bool updating_sign_filters = false; + void _sign_pck_changed(bool p_pressed); + void _sign_filters_changed(const String &p_text); + void _pck_sign_key_priv_changed(const String &p_key); + void _pck_sign_key_pub_changed(const String &p_key); + void _pck_sign_curve_changed(int p_curve); + bool _validate_pck_sign_key(const String &p_priv_key, const String &p_pub_key, int p_curve); + + void _pck_key_gen(); + void _sign_key_gen(); void _open_key_help_link(); diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index f6e3661c7b7f62..7ccc09c2826d87 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -73,3 +73,10 @@ GH-87668 Validate extension JSON: Error: Field 'classes/Font/methods/find_variation/arguments': size changed value in new API, from 8 to 9. Added optional "baseline_offset" argument. Compatibility method registered. + + +GH-87696 +-------- +Validate extension JSON: Error: Field 'classes/PCKPacker/methods/add_file/arguments': size changed value in new API, from 3 to 4. + +Added optional argument. Compatibility method registered. diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 605e82be6e8125..39d77bd0e3b458 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -82,21 +82,7 @@ class EditorExportGDScript : public EditorExportPlugin { GDCLASS(EditorExportGDScript, EditorExportPlugin); public: - virtual void _export_file(const String &p_path, const String &p_type, const HashSet &p_features) override { - String script_key; - - const Ref &preset = get_export_preset(); - - if (preset.is_valid()) { - script_key = preset->get_script_encryption_key().to_lower(); - } - - if (!p_path.ends_with(".gd")) { - return; - } - - return; - } + virtual void _export_file(const String &p_path, const String &p_type, const HashSet &p_features) override {} virtual String get_name() const override { return "GDScript"; } }; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 459f5a5983db42..96e9e429a44834 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -746,7 +746,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj return OK; } -Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key) { +Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed) { APKExportData *ed = static_cast(p_userdata); String dst_path = p_path.replace_first("res://", "assets/"); @@ -754,7 +754,7 @@ Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String return OK; } -Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key) { +Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed) { return OK; } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index c282055fba0e5d..b6dc9cb28c82c1 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -146,9 +146,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Error save_apk_so(void *p_userdata, const SharedObject &p_so); - static Error save_apk_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); + static Error save_apk_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); - static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); + static Error ignore_apk_file(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); @@ -191,7 +191,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static bool _uses_vulkan(); public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); public: virtual void get_preset_features(const Ref &p_preset, List *r_features) const override; diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 09150092359f56..e93aa30748e76d 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -167,7 +167,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) { // It is used by the export_project_files method to save all the asset files into the gradle project. // It's functionality mirrors that of the method save_apk_file. // This method will be called ONLY when gradle build is enabled. -Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key) { +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed) { CustomExportData *export_data = static_cast(p_userdata); String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/"); print_verbose("Saving project files from " + p_path + " into " + dst_path); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 2498394adda196..9700357d2dab66 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -91,7 +91,7 @@ Error store_string_at_path(const String &p_path, const String &p_data); // It is used by the export_project_files method to save all the asset files into the gradle project. // It's functionality mirrors that of the method save_apk_file. // This method will be called ONLY when gradle build is enabled. -Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_source_path, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_sign_in_filters, const Vector &p_sign_ex_filters, const Vector &p_key, bool p_is_signed); // Creates strings.xml files inside the gradle project for different locales. Error _create_project_name_strings_files(const Ref &p_preset, const String &project_name); diff --git a/thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h b/thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h new file mode 100644 index 00000000000000..e7cd7b7c9a17c8 --- /dev/null +++ b/thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* godot_core_ecdsa_mbedtls_config.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GODOT_CORE_ECDSA_MBEDTLS_CONFIG_H +#define GODOT_CORE_ECDSA_MBEDTLS_CONFIG_H + +#include + +// For AES +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CFB +#define MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_MODE_OFB +#define MBEDTLS_CIPHER_MODE_XTS + +#define MBEDTLS_AES_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_MD5_C +#define MBEDTLS_SHA1_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_PLATFORM_ZEROIZE_ALT +#define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES + +// For ECDSA +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +/* Montgomery curves (supporting ECP) */ +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_ECP_DP_CURVE448_ENABLED +#define MBEDTLS_ECP_NIST_OPTIM +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECP_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C + +#endif // GODOT_CORE_ECDSA_MBEDTLS_CONFIG_H