diff --git a/SConstruct b/SConstruct index f4d27a21344..d2d378029bf 100644 --- a/SConstruct +++ b/SConstruct @@ -217,7 +217,11 @@ opts.Add(BoolVariable("disable_advanced_gui", "Disable advanced GUI nodes and be opts.Add("build_profile", "Path to a file containing a feature build profile", "") opts.Add(BoolVariable("modules_enabled_by_default", "If no, disable all modules except ones explicitly enabled", True)) opts.Add(BoolVariable("no_editor_splash", "Don't use the custom splash screen for the editor", True)) -opts.Add("system_certs_path", "Use this path as SSL certificates default for editor (for package maintainers)", "") +opts.Add( + "system_certs_path", + "Use this path as TLS certificates default for editor and Linux/BSD export templates (for package maintainers)", + "", +) opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False)) # Thirdparty libraries diff --git a/core/os/os.h b/core/os/os.h index db1f1f13c9c..1652c1ed904 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -137,6 +137,7 @@ public: virtual String get_stdin_string() = 0; virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) = 0; // Should return cryptographically-safe random bytes. + virtual String get_system_ca_certificates() { return ""; } // Concatenated certificates in PEM format. virtual PackedStringArray get_connected_midi_inputs(); virtual void open_midi_inputs(); diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 6ae36daffe9..68b213d79e8 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -36,6 +36,7 @@ #include "core/config/project_settings.h" #include "core/io/certs_compressed.gen.h" #include "core/io/compression.h" +#include "core/os/os.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -337,20 +338,26 @@ void CryptoMbedTLS::load_default_certificates(String p_path) { if (!p_path.is_empty()) { // Use certs defined in project settings. default_certs->load(p_path); - } + } else { + // Try to use system certs otherwise. + String system_certs = OS::get_singleton()->get_system_ca_certificates(); + if (!system_certs.is_empty()) { + CharString cs = system_certs.utf8(); + default_certs->load_from_memory((const uint8_t *)cs.get_data(), cs.size()); + print_verbose("Loaded system CA certificates"); + } #ifdef BUILTIN_CERTS_ENABLED - else { - // Use builtin certs only if user did not override it in project settings. - PackedByteArray out; - out.resize(_certs_uncompressed_size + 1); - Compression::decompress(out.ptrw(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); - out.write[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator -#ifdef DEBUG_ENABLED - print_verbose("Loaded builtin certs"); + else { + // Use builtin certs if there are no system certs. + PackedByteArray certs; + certs.resize(_certs_uncompressed_size + 1); + Compression::decompress(certs.ptrw(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); + certs.write[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator + default_certs->load_from_memory(certs.ptr(), certs.size()); + print_verbose("Loaded builtin CA certificates"); + } #endif - default_certs->load_from_memory(out.ptr(), out.size()); } -#endif } Ref CryptoMbedTLS::generate_rsa(int p_bytes) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index e111bd18cad..99527ccf3a2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -1044,6 +1044,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC return PermissionsUtil.getGrantedPermissions(getActivity()); } + @Keep + private String getCACertificates() { + return GodotNetUtils.getCACertificates(); + } + /** * The download state should trigger changes in the UI --- it may be useful * to show the state as being indeterminate at times. This sample can be diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java index 401c105cd72..c31d56a3e17 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java @@ -33,11 +33,17 @@ package org.godotengine.godot.utils; import android.app.Activity; import android.content.Context; import android.net.wifi.WifiManager; +import android.util.Base64; import android.util.Log; +import java.io.StringWriter; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + /** * This class handles Android-specific networking functions. - * For now, it only provides access to WifiManager.MulticastLock, which is needed on some devices + * It provides access to the CA certificates KeyStore, and the WifiManager.MulticastLock, which is needed on some devices * to receive broadcast and multicast packets. */ public class GodotNetUtils { @@ -79,4 +85,34 @@ public class GodotNetUtils { Log.e("Godot", "Exception during multicast lock release: " + e); } } + + /** + * Retrieves the list of trusted CA certificates from the "AndroidCAStore" and returns them in PRM format. + * @see https://developer.android.com/reference/java/security/KeyStore . + * @return A string of concatenated X509 certificates in PEM format. + */ + public static String getCACertificates() { + try { + KeyStore ks = KeyStore.getInstance("AndroidCAStore"); + StringBuilder writer = new StringBuilder(); + + if (ks != null) { + ks.load(null, null); + Enumeration aliases = ks.aliases(); + + while (aliases.hasMoreElements()) { + String alias = (String)aliases.nextElement(); + + X509Certificate cert = (X509Certificate)ks.getCertificate(alias); + writer.append("-----BEGIN CERTIFICATE-----\n"); + writer.append(Base64.encodeToString(cert.getEncoded(), Base64.DEFAULT)); + writer.append("-----END CERTIFICATE-----\n"); + } + } + return writer.toString(); + } catch (Exception e) { + Log.e("Godot", "Exception while reading CA certificates: " + e); + return ""; + } + } } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 9d9d0878969..2b504ad69bc 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -70,6 +70,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); _request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z"); _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); + _get_ca_certificates = p_env->GetMethodID(godot_class, "getCACertificates", "()Ljava/lang/String;"); _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;"); _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); @@ -310,6 +311,17 @@ Vector GodotJavaWrapper::get_granted_permissions() const { return permissions_list; } +String GodotJavaWrapper::get_ca_certificates() const { + if (_get_ca_certificates) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, String()); + jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_ca_certificates); + return jstring_to_string(s, env); + } else { + return String(); + } +} + void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 1bd79584d82..05144380e64 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -60,6 +60,7 @@ private: jmethodID _request_permission = nullptr; jmethodID _request_permissions = nullptr; jmethodID _get_granted_permissions = nullptr; + jmethodID _get_ca_certificates = nullptr; jmethodID _init_input_devices = nullptr; jmethodID _get_surface = nullptr; jmethodID _is_activity_resumed = nullptr; @@ -98,6 +99,7 @@ public: bool request_permission(const String &p_name); bool request_permissions(); Vector get_granted_permissions() const; + String get_ca_certificates() const; void init_input_devices(); jobject get_surface(); bool is_activity_resumed(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 1ea742dc6dd..73081e35e7b 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -757,6 +757,10 @@ Error OS_Android::kill(const ProcessID &p_pid) { return OS_Unix::kill(p_pid); } +String OS_Android::get_system_ca_certificates() { + return godot_java->get_ca_certificates(); +} + Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) { r_project_path = get_user_data_dir(); Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 902712d69ee..f1d08b7cfe0 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -160,6 +160,7 @@ public: virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; virtual Error kill(const ProcessID &p_pid) override; + virtual String get_system_ca_certificates() override; virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) override; diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 82500f83cb5..d61faca4150 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -30,6 +30,7 @@ #include "os_linuxbsd.h" +#include "core/io/certs_compressed.gen.h" #include "core/io/dir_access.h" #include "main/main.h" #include "servers/display_server.h" @@ -1080,6 +1081,40 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { return OK; } +String OS_LinuxBSD::get_system_ca_certificates() { + String certfile; + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + // Compile time preferred certificates path. + if (!String(_SYSTEM_CERTS_PATH).is_empty() && da->file_exists(_SYSTEM_CERTS_PATH)) { + certfile = _SYSTEM_CERTS_PATH; + } else if (da->file_exists("/etc/ssl/certs/ca-certificates.crt")) { + // Debian/Ubuntu + certfile = "/etc/ssl/certs/ca-certificates.crt"; + } else if (da->file_exists("/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem")) { + // Fedora + certfile = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"; + } else if (da->file_exists("/etc/ca-certificates/extracted/tls-ca-bundle.pem")) { + // Arch Linux + certfile = "/etc/ca-certificates/extracted/tls-ca-bundle.pem"; + } else if (da->file_exists("/var/lib/ca-certificates/ca-bundle.pem")) { + // openSUSE + certfile = "/var/lib/ca-certificates/ca-bundle.pem"; + } else if (da->file_exists("/etc/ssl/cert.pem")) { + // FreeBSD/OpenBSD + certfile = "/etc/ssl/cert.pem"; + } + + if (certfile.is_empty()) { + return ""; + } + + Ref f = FileAccess::open(certfile, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), "", vformat("Failed to open system CA certificates file: '%s'", certfile)); + + return f->get_as_text(); +} + OS_LinuxBSD::OS_LinuxBSD() { main_loop = nullptr; diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 8dda06b6df6..c1e735b0d47 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -133,6 +133,8 @@ public: virtual Error move_to_trash(const String &p_path) override; + virtual String get_system_ca_certificates() override; + OS_LinuxBSD(); ~OS_LinuxBSD(); }; diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 1fefdb3c68f..7b8d3fd8533 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -235,6 +235,8 @@ def configure(env: "Environment"): "CoreMedia", "-framework", "QuartzCore", + "-framework", + "Security", ] ) env.Append(LIBS=["pthread", "z"]) diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index eb7a30203aa..07bae479be3 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -119,6 +119,8 @@ public: virtual Error move_to_trash(const String &p_path) override; + virtual String get_system_ca_certificates() override; + void run(); OS_MacOS(); diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 74cdef6f259..838ae742fd6 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -30,6 +30,7 @@ #include "os_macos.h" +#include "core/crypto/crypto_core.h" #include "core/version_generated.gen.h" #include "main/main.h" @@ -671,6 +672,34 @@ Error OS_MacOS::move_to_trash(const String &p_path) { return OK; } +String OS_MacOS::get_system_ca_certificates() { + CFArrayRef result; + SecCertificateRef item; + CFDataRef der; + + OSStatus ret = SecTrustCopyAnchorCertificates(&result); + ERR_FAIL_COND_V(ret != noErr, ""); + + CFIndex l = CFArrayGetCount(result); + String certs; + PackedByteArray pba; + for (CFIndex i = 0; i < l; i++) { + item = (SecCertificateRef)CFArrayGetValueAtIndex(result, i); + der = SecCertificateCopyData(item); + int derlen = CFDataGetLength(der); + if (pba.size() < derlen * 3) { + pba.resize(derlen * 3); + } + size_t b64len = 0; + Error err = CryptoCore::b64_encode(pba.ptrw(), pba.size(), &b64len, (unsigned char *)CFDataGetBytePtr(der), derlen); + CFRelease(der); + ERR_CONTINUE(err != OK); + certs += "-----BEGIN CERTIFICATE-----\n" + String((char *)pba.ptr(), b64len) + "\n-----END CERTIFICATE-----\n"; + } + CFRelease(result); + return certs; +} + void OS_MacOS::run() { if (!main_loop) { return; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index cd6d461a938..963f533d67d 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -413,6 +413,7 @@ def configure_msvc(env, vcvars_msvc_config): "dxguid", "imm32", "bcrypt", + "Crypt32", "Avrt", "dwmapi", "dwrite", @@ -592,6 +593,7 @@ def configure_mingw(env): "ksuser", "imm32", "bcrypt", + "crypt32", "avrt", "uuid", "dwmapi", diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index a13d6ed986c..d119bce1bd9 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #ifdef DEBUG_ENABLED #pragma pack(push, before_imagehlp, 8) @@ -1677,6 +1678,26 @@ Error OS_Windows::move_to_trash(const String &p_path) { return OK; } +String OS_Windows::get_system_ca_certificates() { + HCERTSTORE cert_store = CertOpenSystemStoreA(0, "ROOT"); + ERR_FAIL_COND_V_MSG(!cert_store, "", "Failed to read the root certificate store."); + + String certs; + PCCERT_CONTEXT curr = CertEnumCertificatesInStore(cert_store, nullptr); + while (curr) { + DWORD size = 0; + bool success = CryptBinaryToStringA(curr->pbCertEncoded, curr->cbCertEncoded, CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR, nullptr, &size); + ERR_CONTINUE(!success); + PackedByteArray pba; + pba.resize(size); + CryptBinaryToStringA(curr->pbCertEncoded, curr->cbCertEncoded, CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR, (char *)pba.ptrw(), &size); + certs += String((char *)pba.ptr(), size); + curr = CertEnumCertificatesInStore(cert_store, curr); + } + CertCloseStore(cert_store, 0); + return certs; +} + OS_Windows::OS_Windows(HINSTANCE _hInstance) { hInstance = _hInstance; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 960c3f30a9a..c5f95870b31 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -226,6 +226,8 @@ public: virtual Error move_to_trash(const String &p_path) override; + virtual String get_system_ca_certificates() override; + void set_main_window(HWND p_main_window) { main_window = p_main_window; } HINSTANCE get_hinstance() { return hInstance; }