Add Windows TLS engine
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user