[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).
This commit is contained in:
parent
4e1d5be9d3
commit
6fd9982358
17 changed files with 180 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -235,6 +235,8 @@ def configure(env: "Environment"):
|
|||
"CoreMedia",
|
||||
"-framework",
|
||||
"QuartzCore",
|
||||
"-framework",
|
||||
"Security",
|
||||
]
|
||||
)
|
||||
env.Append(LIBS=["pthread", "z"])
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in a new issue