Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion src/tls/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))\
Expand Down Expand Up @@ -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 <dirent.h>
#include <stdio.h>

// 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);
Expand All @@ -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;

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down