certificate: Replace platform bridge with CGO JNI

This commit is contained in:
世界
2026-06-21 17:16:25 +08:00
parent f27d0e38f4
commit 5f265ee4dc
13 changed files with 186 additions and 21 deletions
+4 -10
View File
@@ -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()
+91
View File
@@ -0,0 +1,91 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
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;
}
+34
View File
@@ -0,0 +1,34 @@
//go:build android
package certificate
/*
#include <stdint.h>
#include <stdlib.h>
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
}
+9
View File
@@ -0,0 +1,9 @@
//go:build !android || !cgo
package certificate
import "crypto/x509"
func systemCertificates() []*x509.Certificate {
return nil
}
+13
View File
@@ -0,0 +1,13 @@
#include <jni.h>
#include <stdint.h>
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;
}
+13
View File
@@ -0,0 +1,13 @@
//go:build android
package jni
/*
#include <stdint.h>
extern uintptr_t box_jni_vm(void);
*/
import "C"
func VM() uintptr {
return uintptr(C.box_jni_vm())
}
+7
View File
@@ -0,0 +1,7 @@
//go:build !android || !cgo
package jni
func VM() uintptr {
return 0
}