From f96aea93a6e6b1e4ed2828359bfcc201df51a674 Mon Sep 17 00:00:00 2001 From: Haoxiang Fei Date: Fri, 6 Mar 2026 15:47:52 +0800 Subject: [PATCH] feat: Android OpenSSL/TLS support - Add Android libssl.so dlopen path - BoringSSL compatibility: fall back to SSLeay() when OpenSSL_version_num() is unavailable - Load additional libcrypto functions (PEM_read_X509, X509_STORE_add_cert, X509_free) for Android CA cert loading - Android's /system/etc/security/cacerts/ uses MD5-based hash filenames but OpenSSL 3.x expects SHA1-based hashes; bypass hash lookup by iterating the cert directory and loading each cert directly into the X509_STORE Co-Authored-By: Claude Opus 4.6 --- src/tls/openssl.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 2a6d2a02..3154e907 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -35,6 +35,8 @@ typedef struct BIO BIO; typedef struct SSL SSL; typedef struct SSL_CTX SSL_CTX; typedef struct SSL_METHOD SSL_METHOD; +typedef struct X509 X509; +typedef struct X509_STORE X509_STORE; #define IMPORTED_OPEN_SSL_FUNCTIONS\ IMPORT_FUNC(BIO_METHOD*, BIO_meth_new, (int type, const char *name))\ @@ -69,21 +71,40 @@ typedef struct SSL_METHOD SSL_METHOD; IMPORT_FUNC(long, SSL_CTX_ctrl, (SSL_CTX *ctx, int cmd, long larg, void *parg))\ IMPORT_FUNC(void, SSL_CTX_set_verify, (SSL_CTX *ctx, int mode, int (*verify_cb)(int, void*)))\ IMPORT_FUNC(int, SSL_CTX_set_default_verify_paths, (SSL_CTX *ctx))\ + IMPORT_FUNC(int, SSL_CTX_load_verify_locations, (SSL_CTX *ctx, const char *CAfile, const char *CApath))\ IMPORT_FUNC(unsigned long, ERR_get_error, (void))\ IMPORT_FUNC(char *, ERR_error_string, (unsigned long e, char *buf))\ IMPORT_FUNC(int, RAND_bytes, (unsigned char *buf, int num))\ - IMPORT_FUNC(unsigned char *, SHA1, (const unsigned char *d, size_t n, unsigned char *md)) + IMPORT_FUNC(unsigned char *, SHA1, (const unsigned char *d, size_t n, unsigned char *md))\ + IMPORT_FUNC(X509_STORE *, SSL_CTX_get_cert_store, (const SSL_CTX *ctx)) #define IMPORT_FUNC(ret, name, params) static ret (*name) params; IMPORTED_OPEN_SSL_FUNCTIONS #undef IMPORT_FUNC +#ifdef __ANDROID__ +#include +#include + +// Additional functions from libcrypto needed for Android CA cert loading. +// Android's /system/etc/security/cacerts/ uses MD5-based hash filenames +// (OpenSSL 1.0 style), but OpenSSL 3.x uses SHA1-based hashes for directory +// lookup. We bypass hash lookup by iterating the directory and loading each +// cert directly. +#define X509_FILETYPE_PEM 1 +static X509 *(*PEM_read_X509)(FILE *fp, X509 **x, void *cb, void *u); +static int (*X509_STORE_add_cert)(X509_STORE *store, X509 *x); +static void (*X509_free)(X509 *a); +#endif + int moonbitlang_async_load_openssl(int *major, int *minor, int *fix) { void *handle = 0; #ifdef __MACH__ handle = dlopen("/usr/lib/libssl.48.dylib", RTLD_LAZY); if (!handle) handle = dlopen("/usr/lib/libssl.46.dylib", RTLD_LAZY); +#elif defined(__ANDROID__) + handle = dlopen("libssl.so", RTLD_LAZY); #else handle = dlopen("libssl.so.3", RTLD_LAZY); if (!handle) handle = dlopen("libssl.so.1.1", RTLD_LAZY); @@ -92,6 +113,8 @@ int moonbitlang_async_load_openssl(int *major, int *minor, int *fix) { if (!handle) return 1; unsigned long (*OPENSSL_version_num)() = dlsym(handle, "OpenSSL_version_num"); + if (!OPENSSL_version_num) + OPENSSL_version_num = dlsym(handle, "SSLeay"); // BoringSSL compat if (!OPENSSL_version_num) return 2; @@ -111,6 +134,17 @@ int moonbitlang_async_load_openssl(int *major, int *minor, int *fix) { #undef LOAD_FUNC +#ifdef __ANDROID__ + // Load additional functions from libcrypto for Android CA cert loading. + // libcrypto.so is already loaded as a dependency of libssl.so. + void *crypto_handle = dlopen("libcrypto.so", RTLD_LAZY); + if (crypto_handle) { + PEM_read_X509 = dlsym(crypto_handle, "PEM_read_X509"); + X509_STORE_add_cert = dlsym(crypto_handle, "X509_STORE_add_cert"); + X509_free = dlsym(crypto_handle, "X509_free"); + } +#endif + return 0; } @@ -172,10 +206,41 @@ SSL_CTX *moonbitlang_async_tls_client_ctx() { SSL_CTX *client_ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, 0); +#ifdef __ANDROID__ + // Android's /system/etc/security/cacerts/ uses MD5-based hash filenames, + // but OpenSSL 3.x directory lookup expects SHA1-based hashes. Bypass hash + // lookup by iterating the directory and loading each cert directly. + if (PEM_read_X509 && X509_STORE_add_cert && X509_free) { + X509_STORE *store = SSL_CTX_get_cert_store(client_ctx); + DIR *dir = opendir("/system/etc/security/cacerts"); + if (dir) { + struct dirent *entry; + char path[512]; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + snprintf(path, sizeof(path), "/system/etc/security/cacerts/%s", entry->d_name); + FILE *fp = fopen(path, "r"); + if (fp) { + X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL); + if (cert) { + X509_STORE_add_cert(store, cert); + X509_free(cert); + } + fclose(fp); + } + } + closedir(dir); + } + } else { + // Fallback if libcrypto symbols unavailable + SSL_CTX_set_default_verify_paths(client_ctx); + } +#else if (!SSL_CTX_set_default_verify_paths(client_ctx)) { SSL_CTX_free(client_ctx); return 0; } +#endif SSL_CTX_ctrl(client_ctx, SSL_CTRL_MODE, SSL_MODE_ENABLE_PARTIAL_WRITE, 0); return client_ctx;