Skip to content

Commit

Permalink
[TLS] Add support for platform-specific CA bundles.
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
Faless committed May 11, 2023
1 parent 4e1d5be commit 4f5d1f0
Show file tree
Hide file tree
Showing 17 changed files with 184 additions and 13 deletions.
6 changes: 5 additions & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class OS {
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();
Expand Down
29 changes: 18 additions & 11 deletions modules/mbedtls/crypto_mbedtls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,11 @@ public String[] getGrantedPermissions() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@
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 {
Expand Down Expand Up @@ -79,4 +85,34 @@ public void multicastLockRelease() {
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 "";
}
}
}
12 changes: 12 additions & 0 deletions platform/android/java_godot_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions platform/android/java_godot_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class GodotJavaWrapper {
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;
Expand Down Expand Up @@ -98,6 +99,7 @@ class GodotJavaWrapper {
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();
Expand Down
4 changes: 4 additions & 0 deletions platform/android/os_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions platform/android/os_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class OS_Android : public OS_Unix {
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;

Expand Down
39 changes: 39 additions & 0 deletions platform/linuxbsd/os_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1080,6 +1081,44 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
return OK;
}

String OS_LinuxBSD::get_system_ca_certificates() {
// Compile time certificates path override.
if (!String(_SYSTEM_CERTS_PATH).is_empty()) {
Ref<FileAccess> f = FileAccess::open(_SYSTEM_CERTS_PATH, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), "", vformat("Failed to open system CA certificates file: '%s'", _SYSTEM_CERTS_PATH));
return f->get_as_text();
}

// Look for common Linux/BSD CA certificates locations.
String certfile;
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
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;

Expand Down
2 changes: 2 additions & 0 deletions platform/linuxbsd/os_linuxbsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class OS_LinuxBSD : public OS_Unix {

virtual Error move_to_trash(const String &p_path) override;

virtual String get_system_ca_certificates() override;

OS_LinuxBSD();
~OS_LinuxBSD();
};
Expand Down
2 changes: 2 additions & 0 deletions platform/macos/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ def configure(env: "Environment"):
"CoreMedia",
"-framework",
"QuartzCore",
"-framework",
"Security",
]
)
env.Append(LIBS=["pthread", "z"])
Expand Down
2 changes: 2 additions & 0 deletions platform/macos/os_macos.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class OS_MacOS : public OS_Unix {

virtual Error move_to_trash(const String &p_path) override;

virtual String get_system_ca_certificates() override;

void run();

OS_MacOS();
Expand Down
29 changes: 29 additions & 0 deletions platform/macos/os_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "os_macos.h"

#include "core/crypto/crypto_core.h"
#include "core/version_generated.gen.h"
#include "main/main.h"

Expand Down Expand Up @@ -671,6 +672,34 @@
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;
Expand Down
2 changes: 2 additions & 0 deletions platform/windows/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ def configure_msvc(env, vcvars_msvc_config):
"dxguid",
"imm32",
"bcrypt",
"Crypt32",
"Avrt",
"dwmapi",
"dwrite",
Expand Down Expand Up @@ -592,6 +593,7 @@ def configure_mingw(env):
"ksuser",
"imm32",
"bcrypt",
"crypt32",
"avrt",
"uuid",
"dwmapi",
Expand Down
21 changes: 21 additions & 0 deletions platform/windows/os_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions platform/windows/os_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ class OS_Windows : public OS {

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; }
Expand Down

0 comments on commit 4f5d1f0

Please sign in to comment.