diff options
author | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2023-04-13 21:17:55 +0200 |
---|---|---|
committer | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2023-05-12 09:58:23 +0200 |
commit | 6fd99823581dd05d27a1ff773b67a8ea616993cc (patch) | |
tree | 1baf791a581f4ce5a73433434400546b4a4051fe | |
parent | 4e1d5be9d33e8a14254f0ccd0910743073970413 (diff) | |
download | redot-engine-6fd99823581dd05d27a1ff773b67a8ea616993cc.tar.gz |
[TLS] Add support for platform-specific CA bundles.
Adds a new OS::get_system_ca_certs method which can be implemented by
platforms to retrieve the list of trusted CA certificates using OS
specific APIs.
The function should return the certificates in PEM format, and is
currently implemented for Windows/macOS/LinuxBSD(*)/Android.
mbedTLS will fall back to bundled certificates when the OS returns no
certificates.
(*) LinuxBSD does not have a standardized certificates store location.
The current implementation will test for common locations and may
return an empty string on some distributions (falling back to the
bundled certificates).
-rw-r--r-- | SConstruct | 6 | ||||
-rw-r--r-- | core/os/os.h | 1 | ||||
-rw-r--r-- | modules/mbedtls/crypto_mbedtls.cpp | 29 | ||||
-rw-r--r-- | platform/android/java/lib/src/org/godotengine/godot/Godot.java | 5 | ||||
-rw-r--r-- | platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java | 38 | ||||
-rw-r--r-- | platform/android/java_godot_wrapper.cpp | 12 | ||||
-rw-r--r-- | platform/android/java_godot_wrapper.h | 2 | ||||
-rw-r--r-- | platform/android/os_android.cpp | 4 | ||||
-rw-r--r-- | platform/android/os_android.h | 1 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.cpp | 35 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.h | 2 | ||||
-rw-r--r-- | platform/macos/detect.py | 2 | ||||
-rw-r--r-- | platform/macos/os_macos.h | 2 | ||||
-rw-r--r-- | platform/macos/os_macos.mm | 29 | ||||
-rw-r--r-- | platform/windows/detect.py | 2 | ||||
-rw-r--r-- | platform/windows/os_windows.cpp | 21 | ||||
-rw-r--r-- | platform/windows/os_windows.h | 2 |
17 files changed, 180 insertions, 13 deletions
diff --git a/SConstruct b/SConstruct index f4d27a2134..d2d378029b 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 db1f1f13c9..1652c1ed90 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 6ae36daffe..68b213d79e 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<CryptoKey> 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 e111bd18ca..99527ccf3a 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 401c105cd7..c31d56a3e1 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<String> 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 9d9d087896..2b504ad69b 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<String> 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 1bd79584d8..05144380e6 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<String> 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 1ea742dc6d..73081e35e7 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 902712d69e..f1d08b7cfe 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<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List<String> &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 82500f83cb..d61faca415 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<DirAccess> 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<FileAccess> 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 8dda06b6df..c1e735b0d4 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 1fefdb3c68..7b8d3fd853 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 eb7a30203a..07bae479be 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 74cdef6f25..838ae742fd 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 cd6d461a93..963f533d67 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 a13d6ed986..d119bce1bd 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -55,6 +55,7 @@ #include <regstr.h> #include <shlobj.h> #include <wbemcli.h> +#include <wincrypt.h> #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 960c3f30a9..c5f95870b3 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; } |