diff --git a/adapter/platform.go b/adapter/platform.go index 2e4b0f6f7..448a15daa 100644 --- a/adapter/platform.go +++ b/adapter/platform.go @@ -29,7 +29,6 @@ type PlatformInterface interface { ClearDNSCache() RequestPermissionForWIFIState() error ReadWIFIState() WIFIState - SystemCertificates() []string UsePlatformConnectionOwnerFinder() bool FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error) diff --git a/box.go b/box.go index 7e7b6b7a7..1c129b03d 100644 --- a/box.go +++ b/box.go @@ -187,7 +187,7 @@ func New(options Options) (*Box, error) { len(certificateOptions.Certificate) > 0 || len(certificateOptions.CertificatePath) > 0 || len(certificateOptions.CertificateDirectoryPath) > 0 { - certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions) + certificateStore, err := certificate.NewStore(logFactory.NewLogger("certificate"), certificateOptions) if err != nil { return nil, err } diff --git a/common/certificate/store.go b/common/certificate/store.go index f489267d3..790e12fe4 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -2,7 +2,6 @@ package certificate import ( "bytes" - "context" "crypto/x509" "io/fs" "os" @@ -16,7 +15,6 @@ import ( "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - "github.com/sagernet/sing/service" ) var _ adapter.CertificateStore = (*Store)(nil) @@ -34,7 +32,7 @@ type Store struct { platform storePlatform } -func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) { +func NewStore(logger logger.Logger, options option.CertificateOptions) (*Store, error) { storeType := options.Store if storeType == "" { storeType = C.CertificateStoreSystem @@ -43,14 +41,10 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific switch storeType { case C.CertificateStoreSystem: systemPool = x509.NewCertPool() - platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var systemValid bool - if platformInterface != nil { - for _, cert := range platformInterface.SystemCertificates() { - if systemPool.AppendCertsFromPEM([]byte(cert)) { - systemValid = true - } - } + for _, certificate := range systemCertificates() { + systemPool.AddCert(certificate) + systemValid = true } if !systemValid { certPool, err := x509.SystemCertPool() diff --git a/common/certificate/system_android.c b/common/certificate/system_android.c new file mode 100644 index 000000000..c2421253b --- /dev/null +++ b/common/certificate/system_android.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include + +void *box_system_certificates_der(uintptr_t vmPtr, int *out_length) { + *out_length = 0; + + JavaVM *vm = (JavaVM *) vmPtr; + JNIEnv *env = NULL; + int attached = 0; + jint getEnvResult = (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6); + if (getEnvResult == JNI_EDETACHED) { + if ((*vm)->AttachCurrentThread(vm, &env, NULL) != JNI_OK) { + return NULL; + } + attached = 1; + } else if (getEnvResult != JNI_OK) { + return NULL; + } + + unsigned char *result = NULL; + int resultLength = 0; + + jclass keyStoreClass = (*env)->FindClass(env, "java/security/KeyStore"); + jmethodID getInstance = (*env)->GetStaticMethodID(env, keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;"); + jstring storeName = (*env)->NewStringUTF(env, "AndroidCAStore"); + jobject keyStore = (*env)->CallStaticObjectMethod(env, keyStoreClass, getInstance, storeName); + if ((*env)->ExceptionCheck(env) || keyStore == NULL) { + goto done; + } + + jmethodID load = (*env)->GetMethodID(env, keyStoreClass, "load", "(Ljava/io/InputStream;[C)V"); + (*env)->CallVoidMethod(env, keyStore, load, NULL, NULL); + if ((*env)->ExceptionCheck(env)) { + goto done; + } + + jmethodID aliasesMethod = (*env)->GetMethodID(env, keyStoreClass, "aliases", "()Ljava/util/Enumeration;"); + jmethodID getCertificate = (*env)->GetMethodID(env, keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;"); + jobject aliases = (*env)->CallObjectMethod(env, keyStore, aliasesMethod); + if ((*env)->ExceptionCheck(env) || aliases == NULL) { + goto done; + } + + jclass enumerationClass = (*env)->FindClass(env, "java/util/Enumeration"); + jmethodID hasMoreElements = (*env)->GetMethodID(env, enumerationClass, "hasMoreElements", "()Z"); + jmethodID nextElement = (*env)->GetMethodID(env, enumerationClass, "nextElement", "()Ljava/lang/Object;"); + + jclass certificateClass = (*env)->FindClass(env, "java/security/cert/Certificate"); + jmethodID getEncoded = (*env)->GetMethodID(env, certificateClass, "getEncoded", "()[B"); + + while ((*env)->CallBooleanMethod(env, aliases, hasMoreElements)) { + jstring alias = (jstring) (*env)->CallObjectMethod(env, aliases, nextElement); + jobject certificate = (*env)->CallObjectMethod(env, keyStore, getCertificate, alias); + (*env)->DeleteLocalRef(env, alias); + if ((*env)->ExceptionCheck(env) || certificate == NULL) { + (*env)->ExceptionClear(env); + continue; + } + jbyteArray encoded = (jbyteArray) (*env)->CallObjectMethod(env, certificate, getEncoded); + (*env)->DeleteLocalRef(env, certificate); + if ((*env)->ExceptionCheck(env) || encoded == NULL) { + (*env)->ExceptionClear(env); + continue; + } + jsize encodedLength = (*env)->GetArrayLength(env, encoded); + unsigned char *grown = realloc(result, resultLength + encodedLength); + if (grown == NULL) { + (*env)->DeleteLocalRef(env, encoded); + free(result); + result = NULL; + resultLength = 0; + goto done; + } + result = grown; + (*env)->GetByteArrayRegion(env, encoded, 0, encodedLength, (jbyte *) (result + resultLength)); + resultLength += encodedLength; + (*env)->DeleteLocalRef(env, encoded); + } + +done: + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + } + if (attached) { + (*vm)->DetachCurrentThread(vm); + } + *out_length = resultLength; + return result; +} diff --git a/common/certificate/system_android.go b/common/certificate/system_android.go new file mode 100644 index 000000000..15da22f16 --- /dev/null +++ b/common/certificate/system_android.go @@ -0,0 +1,34 @@ +//go:build android + +package certificate + +/* +#include +#include +extern void *box_system_certificates_der(uintptr_t vm, int *out_length); +*/ +import "C" + +import ( + "crypto/x509" + + "github.com/sagernet/sing-box/common/jni" +) + +func systemCertificates() []*x509.Certificate { + vm := jni.VM() + if vm == 0 { + return nil + } + var length C.int + pointer := C.box_system_certificates_der(C.uintptr_t(vm), &length) + if pointer == nil { + return nil + } + defer C.free(pointer) + certificates, err := x509.ParseCertificates(C.GoBytes(pointer, length)) + if err != nil { + return nil + } + return certificates +} diff --git a/common/certificate/system_other.go b/common/certificate/system_other.go new file mode 100644 index 000000000..2c57a756d --- /dev/null +++ b/common/certificate/system_other.go @@ -0,0 +1,9 @@ +//go:build !android || !cgo + +package certificate + +import "crypto/x509" + +func systemCertificates() []*x509.Certificate { + return nil +} diff --git a/common/jni/jni_android.c b/common/jni/jni_android.c new file mode 100644 index 000000000..74113f797 --- /dev/null +++ b/common/jni/jni_android.c @@ -0,0 +1,13 @@ +#include +#include + +static JavaVM *javaVM; + +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { + javaVM = vm; + return JNI_VERSION_1_6; +} + +uintptr_t box_jni_vm(void) { + return (uintptr_t) javaVM; +} diff --git a/common/jni/jni_android.go b/common/jni/jni_android.go new file mode 100644 index 000000000..8a9aaf767 --- /dev/null +++ b/common/jni/jni_android.go @@ -0,0 +1,13 @@ +//go:build android + +package jni + +/* +#include +extern uintptr_t box_jni_vm(void); +*/ +import "C" + +func VM() uintptr { + return uintptr(C.box_jni_vm()) +} diff --git a/common/jni/jni_other.go b/common/jni/jni_other.go new file mode 100644 index 000000000..c17284e7e --- /dev/null +++ b/common/jni/jni_other.go @@ -0,0 +1,7 @@ +//go:build !android || !cgo + +package jni + +func VM() uintptr { + return 0 +} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 2cc944cdd..08b9d6556 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -131,10 +131,6 @@ func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } -func (s *platformInterfaceStub) SystemCertificates() []string { - return nil -} - func (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool { return false } diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index 69a23d264..25cb778c2 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -18,10 +18,13 @@ import ( "strconv" "sync" + "github.com/sagernet/sing-box/common/certificate" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/protocol/socks" "github.com/sagernet/sing/protocol/socks/socks5" @@ -69,6 +72,7 @@ type httpClient struct { tls tls.Config client http.Client transport http.Transport + store *certificate.Store } func NewHTTPClient() HTTPClient { @@ -78,6 +82,13 @@ func NewHTTPClient() HTTPClient { client.transport.TLSHandshakeTimeout = C.TCPTimeout client.transport.TLSClientConfig = &client.tls client.transport.DisableKeepAlives = true + if C.IsAndroid { + store, err := certificate.NewStore(logger.NOP(), option.CertificateOptions{}) + if err != nil { + panic(E.Cause(err, "initialize certificate store")) + } + client.tls.RootCAs = store.Pool() + } return client } @@ -150,6 +161,9 @@ func (c *httpClient) NewRequest() HTTPRequest { } func (c *httpClient) Close() { + if c.store != nil { + c.store.Close() + } c.transport.CloseIdleConnections() } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index a0726bf7e..39bbc4db0 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -15,7 +15,6 @@ type PlatformInterface interface { UnderNetworkExtension() bool IncludeAllNetworks() bool ReadWIFIState() *WIFIState - SystemCertificates() StringIterator ClearDNSCache() SendNotification(notification *Notification) error StartNeighborMonitor(listener NeighborUpdateListener) error diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 2fdcd6358..9b37fa322 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -175,10 +175,6 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { return (adapter.WIFIState)(*wifiState) } -func (w *platformInterfaceWrapper) SystemCertificates() []string { - return iteratorToArray[string](w.iif.SystemCertificates()) -} - func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool { return true }