diff --git a/adapter/mitm.go b/adapter/mitm.go index 450468a9..c924b784 100644 --- a/adapter/mitm.go +++ b/adapter/mitm.go @@ -2,6 +2,7 @@ package adapter import ( "context" + "crypto/x509" "net" N "github.com/sagernet/sing/common/network" @@ -9,5 +10,6 @@ import ( type MITMEngine interface { Lifecycle + ExportCertificate() *x509.Certificate NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) } diff --git a/experimental/clashapi/mitm.go b/experimental/clashapi/mitm.go new file mode 100644 index 00000000..6afb8895 --- /dev/null +++ b/experimental/clashapi/mitm.go @@ -0,0 +1,84 @@ +package clashapi + +import ( + "context" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/service" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/gofrs/uuid/v5" + "howett.net/plist" +) + +func mitmRouter(ctx context.Context) http.Handler { + r := chi.NewRouter() + r.Get("/mobileconfig", getMobileConfig(ctx)) + r.Get("/certificate", getCertificate(ctx)) + return r +} + +func getMobileConfig(ctx context.Context) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + engine := service.FromContext[adapter.MITMEngine](ctx) + if engine == nil { + http.NotFound(writer, request) + render.PlainText(writer, request, "MITM not enabled") + return + } + certificate := engine.ExportCertificate() + if certificate == nil { + http.NotFound(writer, request) + render.PlainText(writer, request, "Certificate not configured") + return + } + writer.Header().Set("Content-Type", "application/x-apple-aspen-config") + uuidGen := common.Must1(uuid.NewV4()).String() + mobileConfig := map[string]interface{}{ + "PayloadContent": []interface{}{ + map[string]interface{}{ + "PayloadCertificateFileName": "Certificate.cer", + "PayloadContent": certificate.Raw, + "PayloadDescription": "Adds a root certificate", + "PayloadDisplayName": certificate.Subject.CommonName, + "PayloadIdentifier": "com.apple.security.root." + uuidGen, + "PayloadType": "com.apple.security.root", + "PayloadUUID": uuidGen, + "PayloadVersion": 1, + }, + }, + "PayloadDisplayName": certificate.Subject.CommonName, + "PayloadIdentifier": "io.nekohasekai.sfa.ca.profile." + uuidGen, + "PayloadRemovalDisallowed": false, + "PayloadType": "Configuration", + "PayloadUUID": uuidGen, + "PayloadVersion": 1, + } + encoder := plist.NewEncoder(writer) + encoder.Indent("\t") + encoder.Encode(mobileConfig) + } +} + +func getCertificate(ctx context.Context) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + engine := service.FromContext[adapter.MITMEngine](ctx) + if engine == nil { + http.NotFound(writer, request) + render.PlainText(writer, request, "MITM not enabled") + return + } + certificate := engine.ExportCertificate() + if certificate == nil { + http.NotFound(writer, request) + render.PlainText(writer, request, "Certificate not configured") + return + } + writer.Header().Set("Content-Type", "application/x-x509-ca-cert") + writer.Header().Set("Content-Disposition", "attachment; filename=Certificate.crt") + writer.Write(certificate.Raw) + } +} diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 459d205d..e08745b6 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -124,6 +124,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter(ctx)) r.Mount("/dns", dnsRouter(s.dnsRouter)) + r.Mount("/mitm", mitmRouter(ctx)) s.setupMetaAPI(r) }) diff --git a/mitm/engine.go b/mitm/engine.go index b6f502f0..b9b830e1 100644 --- a/mitm/engine.go +++ b/mitm/engine.go @@ -91,6 +91,10 @@ func (e *Engine) Close() error { return nil } +func (e *Engine) ExportCertificate() *x509.Certificate { + return e.tlsCertificate +} + func (e *Engine) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if e.tlsDecryptionEnabled && metadata.ClientHello != nil { err := e.newTLS(ctx, this, conn, metadata, onClose)