Add Windows TLS engine

This commit is contained in:
nekohasekai
2026-04-24 09:04:27 +08:00
committed by 世界
parent 770a0c7499
commit d7f4cefce8
35 changed files with 6513 additions and 488 deletions
+17
View File
@@ -0,0 +1,17 @@
#ifndef BOX_CERTIFICATE_ANCHORS_DARWIN_H
#define BOX_CERTIFICATE_ANCHORS_DARWIN_H
#include <stddef.h>
#include <stdint.h>
// box_certificate_anchors_from_der wraps an array of DER-encoded certificate
// blobs into a retained CFArrayRef of SecCertificateRef, returned as an opaque
// pointer. The caller owns the returned reference and must call
// box_certificate_release_anchors. Returns NULL when no blobs were accepted.
void *box_certificate_anchors_from_der(const uint8_t *const *ders, const size_t *lens, size_t count);
// box_certificate_release_anchors drops one reference from a CFArray handle
// previously returned by box_certificate_anchors_from_der. No-op on NULL.
void box_certificate_release_anchors(void *anchors);
#endif
+42
View File
@@ -0,0 +1,42 @@
#import "anchors_darwin.h"
#import <Foundation/Foundation.h>
#import <Security/Security.h>
void *box_certificate_anchors_from_der(const uint8_t *const *ders, const size_t *lens, size_t count) {
if (count == 0 || ders == NULL || lens == NULL) {
return NULL;
}
CFMutableArrayRef certificates = CFArrayCreateMutable(NULL, (CFIndex)count, &kCFTypeArrayCallBacks);
if (certificates == NULL) {
return NULL;
}
for (size_t index = 0; index < count; index++) {
if (ders[index] == NULL || lens[index] == 0) {
continue;
}
CFDataRef data = CFDataCreate(NULL, ders[index], (CFIndex)lens[index]);
if (data == NULL) {
continue;
}
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, data);
CFRelease(data);
if (certificate == NULL) {
continue;
}
CFArrayAppendValue(certificates, certificate);
CFRelease(certificate);
}
if (CFArrayGetCount(certificates) == 0) {
CFRelease(certificates);
return NULL;
}
return certificates;
}
void box_certificate_release_anchors(void *anchors) {
if (anchors == NULL) {
return;
}
CFRelease((CFTypeRef)anchors);
}
+30 -16
View File
@@ -1,6 +1,7 @@
package certificate
import (
"bytes"
"context"
"crypto/x509"
"io/fs"
@@ -25,11 +26,11 @@ type Store struct {
storeType string
systemPool *x509.CertPool
currentPool *x509.CertPool
currentPEM []string
certificate string
certificatePaths []string
certificateDirectoryPaths []string
watcher *fswatch.Watcher
platform storePlatform
}
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
@@ -114,10 +115,18 @@ func (s *Store) Start(stage adapter.StartStage) error {
}
func (s *Store) Close() error {
if s.watcher != nil {
return s.watcher.Close()
watcher := s.watcher
s.watcher = nil
var closeErr error
if watcher != nil {
closeErr = watcher.Close()
}
return nil
platformErr := s.closePlatform()
if platformErr != nil {
closeErr = platformErr
}
return closeErr
}
func (s *Store) Pool() *x509.CertPool {
@@ -130,37 +139,35 @@ func (s *Store) StoreKind() string {
return s.storeType
}
func (s *Store) CurrentPEM() []string {
s.access.RLock()
defer s.access.RUnlock()
return append([]string(nil), s.currentPEM...)
func (s *Store) ExclusiveAnchors() bool {
return s.storeType != C.CertificateStoreSystem
}
func (s *Store) update() error {
currentPool, err := s.newBasePool()
var currentPEM []string
if err != nil {
return err
}
pemBuffer := new(bytes.Buffer)
switch s.storeType {
case C.CertificateStoreMozilla:
pemContent := mozillaIncludedPEM()
if !currentPool.AppendCertsFromPEM([]byte(pemContent)) {
return E.New("invalid Mozilla included certificate PEM")
}
currentPEM = append(currentPEM, pemContent)
appendPEMBlock(pemBuffer, string(pemContent))
case C.CertificateStoreChrome:
pemContent := chromeIncludedPEM()
if !currentPool.AppendCertsFromPEM([]byte(pemContent)) {
return E.New("invalid Chrome included certificate PEM")
}
currentPEM = append(currentPEM, pemContent)
appendPEMBlock(pemBuffer, string(pemContent))
}
if s.certificate != "" {
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
return E.New("invalid certificate PEM strings")
}
currentPEM = append(currentPEM, s.certificate)
appendPEMBlock(pemBuffer, s.certificate)
}
for _, path := range s.certificatePaths {
pemContent, err := os.ReadFile(path)
@@ -170,7 +177,7 @@ func (s *Store) update() error {
if !currentPool.AppendCertsFromPEM(pemContent) {
return E.New("invalid certificate PEM file: ", path)
}
currentPEM = append(currentPEM, string(pemContent))
appendPEMBlock(pemBuffer, string(pemContent))
}
var firstErr error
for _, directoryPath := range s.certificateDirectoryPaths {
@@ -184,7 +191,7 @@ func (s *Store) update() error {
for _, directoryEntry := range directoryEntries {
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
if err == nil && currentPool.AppendCertsFromPEM(pemContent) {
currentPEM = append(currentPEM, string(pemContent))
appendPEMBlock(pemBuffer, string(pemContent))
}
}
}
@@ -194,8 +201,15 @@ func (s *Store) update() error {
s.access.Lock()
defer s.access.Unlock()
s.currentPool = currentPool
s.currentPEM = currentPEM
return nil
return s.updatePlatformLocked(pemBuffer.Bytes())
}
func appendPEMBlock(buffer *bytes.Buffer, block string) {
existing := buffer.Bytes()
if len(existing) > 0 && existing[len(existing)-1] != '\n' {
buffer.WriteByte('\n')
}
buffer.WriteString(block)
}
func (s *Store) newBasePool() (*x509.CertPool, error) {
+167
View File
@@ -0,0 +1,167 @@
//go:build darwin && cgo
package certificate
/*
#cgo CFLAGS: -x objective-c -fobjc-arc
#cgo LDFLAGS: -framework Foundation -framework Security
#include <stdlib.h>
#include "anchors_darwin.h"
*/
import "C"
import (
"crypto/sha256"
"encoding/pem"
"runtime"
"sync/atomic"
"unsafe"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
)
var (
_ adapter.AppleCertificateStore = (*Store)(nil)
_ adapter.AppleAnchors = (*appleAnchors)(nil)
)
type storePlatform struct {
anchors *appleAnchors
hash [sha256.Size]byte
}
type appleAnchors struct {
cfArray unsafe.Pointer
refs atomic.Int32
}
func newAppleAnchors(pemBytes []byte) (*appleAnchors, error) {
anchors := &appleAnchors{}
anchors.refs.Store(1)
if len(pemBytes) == 0 {
return anchors, nil
}
derBlocks := decodeCertificatePEM(pemBytes)
if len(derBlocks) == 0 {
return nil, E.New("parse certificate PEM")
}
pointerSize := C.size_t(unsafe.Sizeof((*C.uint8_t)(nil)))
lenSize := C.size_t(unsafe.Sizeof(C.size_t(0)))
pointersC := (**C.uint8_t)(C.malloc(pointerSize * C.size_t(len(derBlocks))))
defer C.free(unsafe.Pointer(pointersC))
lensC := (*C.size_t)(C.malloc(lenSize * C.size_t(len(derBlocks))))
defer C.free(unsafe.Pointer(lensC))
pointersSlice := unsafe.Slice(pointersC, len(derBlocks))
lensSlice := unsafe.Slice(lensC, len(derBlocks))
var pinner runtime.Pinner
defer pinner.Unpin()
for index, der := range derBlocks {
pinner.Pin(&der[0])
pointersSlice[index] = (*C.uint8_t)(unsafe.Pointer(&der[0]))
lensSlice[index] = C.size_t(len(der))
}
cfArray := C.box_certificate_anchors_from_der(pointersC, lensC, C.size_t(len(derBlocks)))
if cfArray == nil {
return nil, E.New("parse certificate PEM")
}
anchors.cfArray = cfArray
return anchors, nil
}
// NewAppleAnchors parses the given PEM and returns a ref-counted handle
// wrapping a CFArray of SecCertificateRef. The caller owns the returned
// reference and must call Release when finished. Returns an error when
// pemBytes is non-empty but contains no usable CERTIFICATE blocks.
func NewAppleAnchors(pemBytes []byte) (adapter.AppleAnchors, error) {
return newAppleAnchors(pemBytes)
}
// AcquireAnchors returns a retained AppleAnchors handle, preferring the
// per-config userAnchors over the process-wide certificate store. Returns
// nil when neither source is available. Callers must Release the handle.
func AcquireAnchors(userAnchors adapter.AppleAnchors, store adapter.CertificateStore) adapter.AppleAnchors {
if userAnchors != nil {
return userAnchors.Retain()
}
if store == nil {
return nil
}
apple, loaded := store.(adapter.AppleCertificateStore)
if !loaded {
return nil
}
return apple.AppleAnchors()
}
func (a *appleAnchors) Retain() adapter.AppleAnchors {
a.refs.Add(1)
return a
}
func (a *appleAnchors) Release() {
if a.refs.Add(-1) != 0 {
return
}
if a.cfArray != nil {
C.box_certificate_release_anchors(a.cfArray)
}
}
func (a *appleAnchors) Ref() unsafe.Pointer {
return a.cfArray
}
func (s *Store) AppleAnchors() adapter.AppleAnchors {
s.access.RLock()
defer s.access.RUnlock()
if s.platform.anchors == nil {
return nil
}
return s.platform.anchors.Retain()
}
func (s *Store) updatePlatformLocked(pemBytes []byte) error {
hash := sha256.Sum256(pemBytes)
if s.platform.anchors != nil && s.platform.hash == hash {
return nil
}
newAnchors, err := newAppleAnchors(pemBytes)
if err != nil {
return err
}
old := s.platform.anchors
s.platform.anchors = newAnchors
s.platform.hash = hash
if old != nil {
old.Release()
}
return nil
}
func (s *Store) closePlatform() error {
s.access.Lock()
defer s.access.Unlock()
if s.platform.anchors != nil {
s.platform.anchors.Release()
s.platform.anchors = nil
}
return nil
}
func decodeCertificatePEM(pemBytes []byte) [][]byte {
var blocks [][]byte
rest := pemBytes
for {
block, next := pem.Decode(rest)
if block == nil {
break
}
if block.Type == "CERTIFICATE" && len(block.Bytes) > 0 {
blocks = append(blocks, block.Bytes)
}
rest = next
}
return blocks
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !(darwin && cgo)
package certificate
type storePlatform struct{}
func (s *Store) updatePlatformLocked(_ []byte) error {
return nil
}
func (s *Store) closePlatform() error {
return nil
}