diff --git a/core/SCsub b/core/SCsub index 8bda230b87..f7fa7d1d31 100644 --- a/core/SCsub +++ b/core/SCsub @@ -5,6 +5,7 @@ Import("env") import os +import base64 import core_builders import methods @@ -217,35 +218,79 @@ gen_hash = env.CommandNoCache( env.add_source_files(env.core_sources, gen_hash) -# Generate AES256 script encryption key -def encryption_key_builder(target, source, env): +# Generate encryption and signing keys +def encryption_key_h_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +extern uint8_t pck_encryption_key[32]; +extern uint8_t pck_sign_pub_key[{source[0]}]; +size_t pck_sign_pub_key_len={source[0]};""" + ) + +def encryption_key_cpp_builder(target, source, env): with methods.generated_wrapper(target) as file: file.write( f"""\ #include "core/config/project_settings.h" -uint8_t script_encryption_key[32] = {{ - {source[0]} +uint8_t pck_encryption_key[32] = {{ + {source[0]} +}}; +uint8_t pck_sign_pub_key[{source[1]}] = {{ + {source[2]} }};""" ) +gdkey = "0" * 64 +ec_valid = True +if ("SCRIPT_AES256_ENCRYPTION_KEY" in os.environ) or ("PCK_AES256_ENCRYPTION_KEY" in os.environ): + if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: + gdkey = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"] + elif "PCK_AES256_ENCRYPTION_KEY" in os.environ: + gdkey = os.environ["PCK_AES256_ENCRYPTION_KEY"] + ec_valid = len(gdkey) == 64 + if ec_valid: + try: + gdkey = ",".join([str(int(f"{a}{b}", 16)) for a, b in zip(gdkey[0::2], gdkey[1::2])]) + except Exception: + ec_valid = False + +pubkey = "0" +pubkey_len = 1 +if "PCK_SIGNING_PUBLIC_KEY" in os.environ: + raw_key = base64.b64decode(os.environ["PCK_SIGNING_PUBLIC_KEY"]).hex() + if len(raw_key) >= 64: + pubkey = "" + pubkey_len = len(raw_key) >> 1 + for i in range(pubkey_len): + if i > 0: + pubkey += "," + pubkey += "0x" + raw_key[i * 2 : i * 2 + 2] + env.Append(CPPDEFINES=["PCK_SIGNING_ENABLED"]) -gdkey = os.environ.get("SCRIPT_AES256_ENCRYPTION_KEY", "0" * 64) -ec_valid = len(gdkey) == 64 -if ec_valid: - try: - gdkey = ", ".join([str(int(f"{a}{b}", 16)) for a, b in zip(gdkey[0::2], gdkey[1::2])]) - except Exception: - ec_valid = False if not ec_valid: methods.print_error( f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{gdkey}".\n' - "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) -gen_encrypt = env.CommandNoCache("script_encryption_key.gen.cpp", env.Value(gdkey), env.Run(encryption_key_builder)) -env.add_source_files(env.core_sources, gen_encrypt) + +env.Depends( + "#core/script_encryption_key.gen.h", + [env.Value(pubkey_len)], +) +env.CommandNoCache("#core/script_encryption_key.gen.h", env.Value(pubkey_len), env.Run(encryption_key_h_builder)) +env.Depends( + "#core/script_encryption_key.gen.cpp", + [env.Value(gdkey), env.Value(pubkey_len), env.Value(pubkey)], +) +gen_encrypt_cpp = env.CommandNoCache("#core/script_encryption_key.gen.cpp", [env.Value(gdkey), env.Value(pubkey_len), env.Value(pubkey)], env.Run(encryption_key_cpp_builder)) +env.add_source_files(env.core_sources, gen_encrypt_cpp) + +if env["module_mbedtls_enabled"] or env.editor_build or "PCK_SIGNING_PUBLIC_KEY" in os.environ: + env.Append(CPPDEFINES=["ECDSA_ENABLED"]) # Certificates diff --git a/core/crypto/SCsub b/core/crypto/SCsub index 3cea6bfb47..7d67b6060f 100644 --- a/core/crypto/SCsub +++ b/core/crypto/SCsub @@ -3,6 +3,8 @@ from misc.utility.scons_hints import * Import("env") +import os + env_crypto = env.Clone() is_builtin = env["builtin_mbedtls"] @@ -22,9 +24,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 - config_path = "thirdparty/mbedtls/include/godot_core_mbedtls_config.h" + if env.editor_build or "PCK_SIGNING_PUBLIC_KEY" in os.environ: + config_path = "thirdparty/mbedtls/include/godot_core_ecdsa_mbedtls_config.h" + else: + config_path = "thirdparty/mbedtls/include/godot_core_mbedtls_config.h" + config_path = f"<{config_path}>" if env_crypto["ninja"] and env_crypto.msvc else f'\\"{config_path}\\"' env_crypto.Append(CPPDEFINES=[("MBEDTLS_CONFIG_FILE", config_path)]) + # Build minimal mbedTLS library (MD5/SHA/Base64/AES). env_thirdparty = env_crypto.Clone() env_thirdparty.disable_warnings() @@ -41,10 +48,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 69a83284cc..94c20c3326 100644 --- a/core/crypto/crypto_core.cpp +++ b/core/crypto/crypto_core.cpp @@ -32,6 +32,7 @@ #include "core/os/os.h" +#define MBEDTLS_ALLOW_PRIVATE_ACCESS #include #include #include @@ -43,6 +44,9 @@ #include #endif +#include +#include + // RandomGenerator CryptoCore::RandomGenerator::RandomGenerator() { entropy = memalloc(sizeof(mbedtls_entropy_context)); @@ -249,3 +253,106 @@ 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); + size_t key_length = 0; + CHECK_COND_V(mbedtls_ecp_write_key_ext((mbedtls_ecp_keypair *)keypair, &key_length, (unsigned char *)p_priv_key, len) != 0, FAILED); + *r_priv_len = key_length; + 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, 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 4a9ffda842..40f75e0fc1 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 1340382eaa..7ee74ad676 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) { @@ -134,6 +148,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() { if (singleton == this) { singleton = nullptr; @@ -223,7 +259,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(); @@ -232,7 +272,17 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED); bool rel_filebase = (pack_flags & PACK_REL_FILEBASE); - 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(); } @@ -243,6 +293,16 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, file_base += pck_start_pos; } +#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(); @@ -251,7 +311,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); @@ -259,30 +319,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)); } ////////////////////////////////////////////////////////////////// @@ -383,10 +503,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; @@ -394,21 +514,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 57b7a5f87f..4a12a61763 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -40,8 +40,13 @@ // 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, @@ -49,7 +54,8 @@ enum PackFlags { }; enum PackFileFlags { - PACK_FILE_ENCRYPTED = 1 << 0 + PACK_FILE_ENCRYPTED = 1 << 0, + PACK_FILE_REQUIRE_VERIFICATION = 1 << 1, }; class PackSource; @@ -62,11 +68,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: @@ -97,6 +107,8 @@ class PackedData { } }; + static HashSet require_verification; + static HashSet require_encryption; HashMap files; Vector sources; @@ -109,9 +121,12 @@ 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 uint8_t *get_file_hash(const String &p_path); + 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; } @@ -186,7 +201,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 b33b7b35c3..b3dc3876d4 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 0000000000..884d64317c --- /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 93179d9a11..8c92bdd234 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" @@ -47,9 +48,25 @@ static int _get_pad(int p_alignment, int p_n) { } void PCKPacker::_bind_methods() { - ClassDB::bind_method(D_METHOD("pck_start", "pck_path", "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("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", "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(ECP_DP_SECP256R1), DEFVAL(false)); + + BIND_ENUM_CONSTANT(ECP_DP_NONE); + BIND_ENUM_CONSTANT(ECP_DP_SECP192R1); + BIND_ENUM_CONSTANT(ECP_DP_SECP224R1); + BIND_ENUM_CONSTANT(ECP_DP_SECP256R1); + BIND_ENUM_CONSTANT(ECP_DP_SECP384R1); + BIND_ENUM_CONSTANT(ECP_DP_SECP521R1); + BIND_ENUM_CONSTANT(ECP_DP_BP256R1); + BIND_ENUM_CONSTANT(ECP_DP_BP384R1); + BIND_ENUM_CONSTANT(ECP_DP_BP512R1); + BIND_ENUM_CONSTANT(ECP_DP_CURVE25519); + BIND_ENUM_CONSTANT(ECP_DP_SECP192K1); + BIND_ENUM_CONSTANT(ECP_DP_SECP224K1); + BIND_ENUM_CONSTANT(ECP_DP_SECP256K1); + BIND_ENUM_CONSTANT(ECP_DP_CURVE448); } Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const String &p_key, bool p_encrypt_directory) { @@ -106,7 +123,7 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri return OK; } -Error PCKPacker::add_file(const String &p_pck_path, 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); @@ -117,21 +134,21 @@ Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_ File pf; // Simplify path here and on every 'files' access so that paths that have extra '/' // symbols in them still match to the MD5 hash for the saved path. - pf.path = p_pck_path.simplify_path(); + pf.path = p_file.simplify_path(); pf.src_path = p_src; pf.ofs = ofs; pf.size = f->get_length(); 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 +169,27 @@ Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_ } Error PCKPacker::flush(bool p_verbose) { + return flush_and_sign(String(), ECP_DP_NONE, p_verbose); +} + +Error PCKPacker::flush_and_sign(const String &p_private_key, PCKPacker::CurveType 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 +209,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 +326,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 5aac833532..2223df00f1 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -38,6 +38,25 @@ class FileAccess; class PCKPacker : public RefCounted { GDCLASS(PCKPacker, RefCounted); +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: Ref file; int alignment = 0; uint64_t ofs = 0; @@ -53,16 +72,27 @@ 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_pck_path, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); - Error add_file(const String &p_pck_path, const String &p_src, bool p_encrypt = false); + 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, bool require_verification = false); Error flush(bool p_verbose = false); + Error flush_and_sign(const String &p_private_key, PCKPacker::CurveType p_curve, bool p_verbose = false); PCKPacker() {} }; +VARIANT_ENUM_CAST(PCKPacker::CurveType); + #endif // PCK_PACKER_H diff --git a/core/object/script_language.h b/core/object/script_language.h index 3ddfbb3e7d..028e08bdf7 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -429,8 +429,6 @@ class ScriptLanguage : public Object { VARIANT_ENUM_CAST(ScriptLanguage::ScriptNameCasing); -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 ec0300c068..ecd6c321e1 100644 --- a/doc/classes/PCKPacker.xml +++ b/doc/classes/PCKPacker.xml @@ -29,15 +29,27 @@ + Adds the [param source_path] file to the current PCK package at the [param pck_path] internal path (should start with [code]res://[/code]). + If [param encrypt] is [code]true[/code] and valid encryption key was passed to [method pck_start], this file will be encrypted. + If [param require_verification] is [code]true[/code], this file will require hash validating before reading it. - 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] and [param curve]. If [param verbose] is [code]true[/code], a list of files added will be printed to the console for easier debugging. @@ -51,4 +63,48 @@ + + + Signing disabled. + + + 192-bit curve defined by FIPS 186-4 and SEC1 (SECP192R1) + + + 224-bit curve defined by FIPS 186-4 and SEC1 (SECP224R1) + + + 256-bit curve defined by FIPS 186-4 and SEC1 (SECP256R1) + + + 384-bit curve defined by FIPS 186-4 and SEC1 (SECP384R1) + + + 521-bit curve defined by FIPS 186-4 and SEC1 (SECP521R1) + + + 256-bit Brainpool curve (BP256R1) + + + 384-bit Brainpool curve (BP384R1) + + + 512-bit Brainpool curve (BP512R1) + + + Curve25519 (CURVE25519) + + + 192-bit Koblitz curve (SECP192K1) + + + 224-bit Koblitz curve (SECP224K1) + + + 256-bit Koblitz curve (SECP256K1) + + + Curve448-Goldilocks (CURVE448) + + diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 6ca83c5e25..efaf9f3444 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -90,7 +90,13 @@ void EditorExport::_save() { config->set_value(section, "encrypt_pck", preset->get_enc_pck()); config->set_value(section, "encrypt_directory", preset->get_enc_directory()); config->set_value(section, "script_export_mode", preset->get_script_export_mode()); - 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"; @@ -319,8 +325,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 58737c53ed..10f7357161 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -205,28 +205,54 @@ void EditorExportPlatform::_unload_patches() { PackedData::get_singleton()->clear(); } -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) { - ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); +Error EditorExportPlatform::_save_pack_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { + ERR_FAIL_COND_V_MSG(p_info.total_files < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); PackData *pd = (PackData *)p_userdata; SavedData sd; - sd.path_utf8 = p_path.utf8(); + sd.path_utf8 = p_info.path.utf8(); sd.ofs = pd->f->get_position(); 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_info.enc_key.is_empty()) { + if (PackedData::file_require_encryption(p_info.path)) { sd.encrypted = true; - break; + } else { + for (int i = 0; i < p_info.enc_in_filters.size(); ++i) { + if (p_info.path.matchn(p_info.enc_in_filters[i]) || p_info.path.replace("res://", "").matchn(p_info.enc_in_filters[i]) || p_info.source_path.matchn(p_info.enc_in_filters[i]) || p_info.source_path.replace("res://", "").matchn(p_info.enc_in_filters[i])) { + sd.encrypted = true; + break; + } + } + + for (int i = 0; i < p_info.enc_ex_filters.size(); ++i) { + if (p_info.path.matchn(p_info.enc_ex_filters[i]) || p_info.path.replace("res://", "").matchn(p_info.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_info.is_signed) { + if (PackedData::file_require_verification(p_info.path)) { + sd.require_verification = true; + } else { + for (int i = 0; i < p_info.sign_in_filters.size(); ++i) { + if (p_info.path.matchn(p_info.sign_in_filters[i]) || p_info.path.replace("res://", "").matchn(p_info.sign_in_filters[i]) || p_info.source_path.matchn(p_info.sign_in_filters[i]) || p_info.source_path.replace("res://", "").matchn(p_info.sign_in_filters[i])) { + sd.require_verification = true; + break; + } + } + + for (int i = 0; i < p_info.sign_ex_filters.size(); ++i) { + if (p_info.path.matchn(p_info.sign_ex_filters[i]) || p_info.path.replace("res://", "").matchn(p_info.sign_ex_filters[i])) { + sd.require_verification = false; + break; + } + } } } @@ -237,7 +263,7 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa fae.instantiate(); ERR_FAIL_COND_V(fae.is_null(), ERR_SKIP); - Error err = fae->open_and_parse(ftmp, p_key, FileAccessEncrypted::MODE_WRITE_AES256, false); + Error err = fae->open_and_parse(ftmp, p_info.enc_key, FileAccessEncrypted::MODE_WRITE_AES256, false); ERR_FAIL_COND_V(err != OK, ERR_SKIP); ftmp = fae; } @@ -257,36 +283,35 @@ 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); // TRANSLATORS: This is an editor progress label describing the storing of a file. - if (pd->ep->step(vformat(TTR("Storing File: %s"), p_path), 2 + p_file * 100 / p_total, false)) { + if (pd->ep->step(vformat(TTR("Storing File: %s"), p_info.path), 2 + p_info.file_index * 100 / p_info.total_files, false)) { return ERR_SKIP; } return OK; } -Error EditorExportPlatform::_save_pack_patch_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) { - if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) { +Error EditorExportPlatform::_save_pack_patch_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { + if (_check_hash(PackedData::get_singleton()->get_file_hash(p_info.path), p_data)) { return OK; } - return _save_pack_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key); + return _save_pack_file(p_userdata, p_info, p_data); } -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) { - ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); +Error EditorExportPlatform::_save_zip_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { + ERR_FAIL_COND_V_MSG(p_info.total_files < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); - String path = p_path.replace_first("res://", ""); + String path = p_info.path.replace_first("res://", ""); ZipData *zd = (ZipData *)p_userdata; @@ -308,19 +333,19 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat zd->file_count += 1; - if (zd->ep->step(TTR("Storing File:") + " " + p_path, 2 + p_file * 100 / p_total, false)) { + if (zd->ep->step(TTR("Storing File:") + " " + p_info.path, 2 + p_info.file_index * 100 / p_info.total_files, false)) { return ERR_SKIP; } return OK; } -Error EditorExportPlatform::_save_zip_patch_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) { - if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) { +Error EditorExportPlatform::_save_zip_patch_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { + if (_check_hash(PackedData::get_singleton()->get_file_hash(p_info.path), p_data)) { return OK; } - return _save_zip_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key); + return _save_zip_file(p_userdata, p_info, p_data); } Ref EditorExportPlatform::get_option_icon(int p_index) const { @@ -866,12 +891,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() { @@ -923,17 +978,17 @@ Vector EditorExportPlatform::get_forced_export_files() { return files; } -Error EditorExportPlatform::_script_save_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::_script_save_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb; ERR_FAIL_COND_V(!cb.is_valid(), FAILED); - Variant path = p_path; + Variant path = p_info.path; Variant data = p_data; - Variant file = p_file; - Variant total = p_total; - Variant enc_in = p_enc_in_filters; - Variant enc_ex = p_enc_ex_filters; - Variant enc_key = p_key; + Variant file = p_info.file_index; + Variant total = p_info.total_files; + Variant enc_in = p_info.enc_in_filters; + Variant enc_ex = p_info.enc_ex_filters; + Variant enc_key = p_info.enc_key; Variant ret; Callable::CallError ce; @@ -965,11 +1020,24 @@ Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const Sh return (Error)ret.operator int(); } +Error EditorExportPlatform::_script_save_file_adapter(void *p_userdata, const ExportFileData &p_info, const Vector &p_data) { + ScriptCallbackData *data = static_cast(p_userdata); + Variant result = data->file_cb.call(p_info.path, p_data, p_info.file_index, p_info.total_files, p_info.enc_in_filters, p_info.enc_ex_filters, p_info.enc_key); + + // Check if the result is valid and can be converted to an int (Error) + if (result.get_type() == Variant::INT) { + return static_cast(static_cast(result)); + } else { + // Handle the case where the callback didn't return a valid Error + return ERR_INVALID_DATA; + } +} + Error EditorExportPlatform::_export_project_files(const Ref &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) { - ScriptCallbackData data; - data.file_cb = p_save_func; - data.so_cb = p_so_func; - return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object); + ScriptCallbackData data; + data.file_cb = p_save_func; + data.so_cb = p_so_func; + return export_project_files(p_preset, p_debug, &_script_save_file_adapter, &data, _script_add_shared_object); } Error EditorExportPlatform::export_project_files(const Ref &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { @@ -1031,8 +1099,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) { @@ -1055,13 +1126,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') { @@ -1070,8 +1141,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') { @@ -1084,6 +1155,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(); @@ -1096,6 +1187,13 @@ Error EditorExportPlatform::export_project_files(const Ref & // Always sort by name, to so if for some reason they are re-arranged, it still works. export_plugins.sort_custom(); + ExportFileData file_info; + file_info.enc_in_filters = enc_in_filters; + file_info.enc_ex_filters = enc_ex_filters; + file_info.sign_in_filters = sign_in_filters; + file_info.sign_ex_filters = sign_ex_filters; + file_info.enc_key = key; + file_info.is_signed = sign_pck; for (int i = 0; i < export_plugins.size(); i++) { if (p_so_func) { for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) { @@ -1106,7 +1204,11 @@ 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); + file_info.path = export_plugins[i]->extra_files[j].path; + file_info.source_path = export_plugins[i]->extra_files[j].path; + file_info.file_index = j; + file_info.total_files = paths.size(); + err = p_func(p_udata, file_info, export_plugins[i]->extra_files[j].data); if (err != OK) { return err; } @@ -1222,7 +1324,11 @@ 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); + file_info.path = export_plugins[i]->extra_files[j].path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, export_plugins[i]->extra_files[j].data); if (err != OK) { return err; } @@ -1252,7 +1358,11 @@ 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); + file_info.path = path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, array); if (err != OK) { return err; @@ -1295,13 +1405,21 @@ 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); + file_info.path = path + ".import"; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, sarr); if (err != OK) { return err; } // Now actual remapped file: + file_info.path = export_path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; 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, file_info, sarr); if (err != OK) { return err; } @@ -1330,15 +1448,23 @@ Error EditorExportPlatform::export_project_files(const Ref & String remap = F; if (remap == "path") { String remapped_path = config->get_value("remap", remap); + file_info.path = remapped_path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; 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, file_info, array); } 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); + file_info.path = remapped_path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; 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, file_info, array); } else { // Remove paths if feature not enabled. config->erase_section_key("remap", remap); @@ -1364,7 +1490,11 @@ 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); + file_info.path = path + ".import"; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, sarr); if (err != OK) { return err; @@ -1385,7 +1515,11 @@ 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); + file_info.path = export_path; + file_info.source_path = path; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, array); if (err != OK) { return err; } @@ -1449,7 +1583,11 @@ 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); + file_info.path = from + ".remap"; + file_info.source_path = from; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, new_file); if (err != OK) { return err; } @@ -1463,7 +1601,11 @@ 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); + file_info.path = forced_export[i]; + file_info.source_path = forced_export[i]; + file_info.file_index = idx; + file_info.total_files = total; + err = p_func(p_udata, file_info, array); if (err != OK) { return err; } @@ -1475,7 +1617,11 @@ 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); + file_info.path = "res://" + config_file; + file_info.source_path = "res://" + config_file; + file_info.file_index = idx; + file_info.total_files = total; + return p_func(p_udata, file_info, data); } Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) { @@ -1788,6 +1934,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) { @@ -1801,7 +1948,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); } @@ -1812,14 +1964,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') { @@ -1828,8 +1980,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') { @@ -1855,24 +2007,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()) { @@ -1916,6 +2113,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 ef3274c5e4..e45434e9db 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,19 @@ 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); + struct ExportFileData { + String source_path; + String path; + int file_index = 0; + int total_files = 0; + Vector enc_in_filters; + Vector enc_ex_filters; + Vector sign_in_filters; + Vector sign_ex_filters; + Vector enc_key; + bool is_signed = false; + }; + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const ExportFileData &p_info, const Vector &p_data); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); enum DebugFlags { @@ -82,7 +100,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 { @@ -111,13 +131,13 @@ class EditorExportPlatform : public RefCounted { void _export_find_dependencies(const String &p_path, HashSet &p_paths); static bool _check_hash(const uint8_t *p_hash, const Vector &p_data); - - 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_pack_patch_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 ExportFileData &p_info, const Vector &p_data); + static Error _save_pack_patch_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data); static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so); - 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_zip_patch_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 ExportFileData &p_info, const Vector &p_data); + static Error _save_zip_patch_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data); static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so); struct ScriptCallbackData { @@ -125,9 +145,10 @@ class EditorExportPlatform : public RefCounted { Callable so_cb; }; - static Error _script_save_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 _script_save_file(void *p_userdata, const ExportFileData &p_info, const Vector &p_data); static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so); - + static Error _script_save_file_adapter(void *p_userdata, const ExportFileData &p_info, const Vector &p_data); + 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); @@ -145,7 +166,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 1ca72348e2..786feef355 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -85,7 +85,9 @@ void EditorExportPreset::_bind_methods() { ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter); ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck); ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory); - ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key); + ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_pck_encryption_key); + ClassDB::bind_method(D_METHOD("get_signing_curve"), &EditorExportPreset::get_pck_signing_curve); + ClassDB::bind_method(D_METHOD("get_signing_key_pub"), &EditorExportPreset::get_pck_signing_key_pub); ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode); ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env); @@ -448,12 +450,12 @@ 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; } @@ -466,6 +468,60 @@ int EditorExportPreset::get_script_export_mode() const { return script_mode; } +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 af3a23fc50..5eec188e75 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -88,7 +88,7 @@ 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; @@ -96,6 +96,14 @@ class EditorExportPreset : public RefCounted { String script_key; int script_mode = MODE_SCRIPT_BINARY_TOKENS_COMPRESSED; + 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; @@ -171,8 +179,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; void set_script_export_mode(int p_mode); int get_script_export_mode() const; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index f9137082d7..abbe497009 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" @@ -385,27 +386,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(); } int script_export_mode = current->get_script_export_mode(); @@ -570,8 +606,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) { @@ -586,7 +638,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(); } @@ -604,7 +676,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; } @@ -612,14 +684,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) { @@ -641,6 +713,142 @@ void ProjectExportDialog::_script_export_mode_changed(int p_mode) { _update_current_preset(); } +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()) { @@ -1582,55 +1790,132 @@ ProjectExportDialog::ProjectExportDialog() { feature_vb->add_margin_child(TTR("Feature List:"), custom_feature_display, true); sections->add_child(feature_vb); - // Encryption export parameters. + // Pack encryption parameters. + ScrollContainer *enc_scroll_container = memnew(ScrollContainer); + enc_scroll_container->set_name(TTR("Encryption")); + enc_scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - ScrollContainer *sec_scroll_container = memnew(ScrollContainer); - sec_scroll_container->set_name(TTR("Encryption")); - sec_scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - - VBoxContainer *sec_vb = memnew(VBoxContainer); - sec_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - sec_scroll_container->add_child(sec_vb); + VBoxContainer *enc_vb = memnew(VBoxContainer); + enc_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + enc_scroll_container->add_child(enc_vb); + enc_vb->set_name(TTR("Encryption")); enc_pck = memnew(CheckButton); enc_pck->connect(SceneStringName(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(SceneStringName(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(SceneStringName(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(SceneStringName(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(SceneStringName(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(SceneStringName(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_scroll_container); + 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(SceneStringName(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_scroll_container); + + // Pack signing parameters. + ScrollContainer *sign_scroll_container = memnew(ScrollContainer); + sign_scroll_container->set_name(TTR("Signing")); + sign_scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + + VBoxContainer *sign_vb = memnew(VBoxContainer); + sign_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sign_scroll_container->add_child(sign_vb); + 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_scroll_container); // Script export parameters. @@ -1655,7 +1940,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 e360596be6..7c892d97f7 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -115,8 +115,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; @@ -174,6 +181,9 @@ class ProjectExportDialog : public ConfirmationDialog { LineEdit *enc_ex_filters = nullptr; OptionButton *script_mode = nullptr; + CheckButton *sign_pck = nullptr; + LineEdit *sign_in_filters = nullptr; + LineEdit *sign_ex_filters = nullptr; void _open_export_template_manager(); @@ -190,13 +200,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 _script_export_mode_changed(int p_mode); diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 06933b5841..ff5eda7537 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -87,3 +87,10 @@ GH-94684 Validate extension JSON: Error: Field 'classes/SoftBody3D/methods/set_point_pinned/arguments': size changed value in new API, from 3 to 4. Optional argument added to allow for adding pin point at specific index. 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/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index cfd258cddc..5891e98bfd 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -783,15 +783,15 @@ 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 ExportFileData &p_info, const Vector &p_data) { APKExportData *ed = static_cast(p_userdata); - String dst_path = p_path.replace_first("res://", "assets/"); + String dst_path = p_info.path.replace_first("res://", "assets/"); - store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0); + store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_info.path, p_data) ? Z_DEFLATED : 0); 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 ExportFileData &p_info, const Vector &p_data) { return OK; } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 7e1d626486..23f276e6f5 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -141,9 +141,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 ExportFileData &p_info, const Vector &p_data); - 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 ExportFileData &p_info, const Vector &p_data); static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); @@ -185,7 +185,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { void _notification(int p_what); 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 ExportFileData &p_info, const Vector &p_data); 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 9eddef6a4c..44d3bc8d66 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -167,10 +167,10 @@ 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 EditorExportPlatform::ExportFileData &p_info, const Vector &p_data) { 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); + String dst_path = p_info.path.replace_first("res://", export_data->assets_directory + "/"); + print_verbose("Saving project files from " + p_info.path + " into " + dst_path); Error err = store_file_at_path(dst_path, p_data); return err; } diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 9f8e476f73..0b0562d6c9 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -92,7 +92,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 EditorExportPlatform::ExportFileData &p_info, const Vector &p_data); // 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, const String &p_gradle_build_dir); 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 0000000000..e7cd7b7c9a --- /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