Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified build.sh
100644 → 100755
Empty file.
125 changes: 125 additions & 0 deletions pkg/mitmdf/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package mitmdf

import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
)

// AssetList mirrors the geosite/geoip filenames expected by xray-core.
var requiredAssets = []struct {
Name string
URL string
Display string
}{
{
Name: "geosite.dat",
URL: "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat",
Display: "geosite.dat (domain list)",
},
{
Name: "geoip.dat",
URL: "https://github.com/v2fly/geoip/releases/latest/download/geoip.dat",
Display: "geoip.dat (IP ranges)",
},
}

func AssetsDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("cannot find home dir: %w", err)
}
dir := filepath.Join(home, ".xray-knife", "assets")
if err := os.MkdirAll(dir, 0755); err != nil {
return "", fmt.Errorf("cannot create assets dir %s: %w", dir, err)
}
return dir, nil
}

type AssetStatus struct {
Geosite bool `json:"geosite"`
Geoip bool `json:"geoip"`
}

func CheckAssets() (*AssetStatus, error) {
dir, err := AssetsDir()
if err != nil {
return nil, err
}
status := &AssetStatus{}
if _, err := os.Stat(filepath.Join(dir, "geosite.dat")); err == nil {
status.Geosite = true
}
if _, err := os.Stat(filepath.Join(dir, "geoip.dat")); err == nil {
status.Geoip = true
}
return status, nil
}

type AssetProgress struct {
Current string `json:"current,omitempty"`
Total int `json:"total"`
Done int `json:"done"`
Error string `json:"error,omitempty"`
}

type AssetCallback func(progress AssetProgress)

func DownloadAssets(cb AssetCallback) error {
dir, err := AssetsDir()
if err != nil {
return err
}

client := &http.Client{Timeout: 5 * time.Minute}

for i, asset := range requiredAssets {
dest := filepath.Join(dir, asset.Name)

if cb != nil {
cb(AssetProgress{Current: asset.Display, Total: len(requiredAssets), Done: i})
}

tmpDest := dest + ".tmp"
out, err := os.Create(tmpDest)
if err != nil {
return fmt.Errorf("failed to create %s: %w", asset.Name, err)
}

resp, err := client.Get(asset.URL)
if err != nil {
out.Close()
os.Remove(tmpDest)
return fmt.Errorf("failed to download %s: %w", asset.Display, err)
}

if resp.StatusCode != http.StatusOK {
out.Close()
resp.Body.Close()
os.Remove(tmpDest)
return fmt.Errorf("bad status downloading %s: %s", asset.Display, resp.Status)
}

if _, err := io.Copy(out, resp.Body); err != nil {
out.Close()
resp.Body.Close()
os.Remove(tmpDest)
return fmt.Errorf("failed to write %s: %w", asset.Name, err)
}
out.Close()
resp.Body.Close()

if err := os.Rename(tmpDest, dest); err != nil {
return fmt.Errorf("failed to rename %s: %w", asset.Name, err)
}

if cb != nil {
cb(AssetProgress{Current: asset.Display, Total: len(requiredAssets), Done: i + 1})
}
}

return nil
}
229 changes: 229 additions & 0 deletions pkg/mitmdf/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package mitmdf

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
)

func GenerateCertificate(certPath, keyPath string, force bool) error {
if !force {
if _, err := os.Stat(certPath); err == nil {
if _, err := os.Stat(keyPath); err == nil {
return nil
}
}
}

dir := filepath.Dir(certPath)
if dir != "" {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create cert directory: %w", err)
}
}

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return fmt.Errorf("failed to generate RSA key: %w", err)
}

serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return fmt.Errorf("failed to generate serial: %w", err)
}

template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: "MITM-DomainFronting Root CA",
Organization: []string{"MITM-DomainFronting"},
},
NotBefore: time.Now().Add(-24 * time.Hour),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
DNSNames: []string{"localhost", "fromMitM"},
}

certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}

certFile, err := os.Create(certPath)
if err != nil {
return fmt.Errorf("failed to create cert file: %w", err)
}
defer certFile.Close()

if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
return fmt.Errorf("failed to write cert PEM: %w", err)
}

keyFile, err := os.Create(keyPath)
if err != nil {
return fmt.Errorf("failed to create key file: %w", err)
}
defer keyFile.Close()

keyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
if err := pem.Encode(keyFile, keyPEM); err != nil {
return fmt.Errorf("failed to write key PEM: %w", err)
}

return nil
}

func CheckCertificate(certPath, keyPath string) bool {
if _, err := os.Stat(certPath); os.IsNotExist(err) {
return false
}
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
return false
}
if data, err := os.ReadFile(certPath); err == nil {
block, _ := pem.Decode(data)
if block != nil && block.Type == "CERTIFICATE" {
if _, err := x509.ParseCertificate(block.Bytes); err == nil {
return true
}
}
}
return false
}

type InstallResult struct {
Success bool `json:"success"`
Message string `json:"message"`
Command string `json:"command,omitempty"`
}

func InstallCert(certPath string) InstallResult {
if runtime.GOOS != "linux" {
return InstallResult{
Message: fmt.Sprintf("Auto-install only supported on Linux. Copy %s to your system trust store manually.", certPath),
Command: fmt.Sprintf("cp %s /usr/local/share/ca-certificates/mitmdf-root-ca.crt && sudo update-ca-certificates", certPath),
}
}

data, err := os.ReadFile(certPath)
if err != nil {
return InstallResult{Message: fmt.Sprintf("Cannot read cert file: %v", err)}
}

// Determine system ca store layout
type caLayout struct {
Dir string
Ext string
Cmd string
CmdArgs []string
}

var layouts []caLayout

// Detect distro via available commands
if _, err := exec.LookPath("update-ca-certificates"); err == nil {
layouts = append(layouts, caLayout{
Dir: "/usr/local/share/ca-certificates",
Ext: ".crt",
Cmd: "update-ca-certificates",
})
layouts = append(layouts, caLayout{
Dir: "/usr/share/ca-certificates",
Ext: ".crt",
Cmd: "update-ca-certificates",
})
}
if _, err := exec.LookPath("update-ca-trust"); err == nil {
layouts = append(layouts, caLayout{
Dir: "/etc/pki/ca-trust/source/anchors",
Ext: ".pem",
Cmd: "update-ca-trust",
CmdArgs: []string{"extract"},
})
}
if _, err := exec.LookPath("trust"); err == nil {
layouts = append(layouts, caLayout{
Dir: "/etc/ca-certificates/trust-source/anchors",
Ext: ".pem",
Cmd: "trust",
CmdArgs: []string{"extract-compat"},
})
}

if len(layouts) == 0 {
return InstallResult{
Message: "No supported CA trust update command found. Install the cert manually.",
Command: fmt.Sprintf("cp %s /usr/local/share/ca-certificates/mitmdf-root-ca.crt && sudo update-ca-certificates", certPath),
}
}

firstErr := ""
for _, l := range layouts {
destName := "mitmdf-root-ca" + l.Ext
dest := filepath.Join(l.Dir, destName)

// Try to write directly
if err := os.MkdirAll(l.Dir, 0755); err != nil {
if firstErr == "" {
firstErr = fmt.Sprintf("write %s: %v", dest, err)
}
continue
}
if err := os.WriteFile(dest, data, 0644); err != nil {
if firstErr == "" {
firstErr = fmt.Sprintf("write %s: %v", dest, err)
}
continue
}

cmd := exec.Command(l.Cmd, l.CmdArgs...)
if out, err := cmd.CombinedOutput(); err != nil {
// Command failed but cert file is in place
return InstallResult{
Success: true,
Message: fmt.Sprintf("Copied cert to %s but trust update command failed: %s", dest, string(out)),
Command: fmt.Sprintf("sudo %s %s", l.Cmd, joinArgs(l.CmdArgs)),
}
}

return InstallResult{
Success: true,
Message: fmt.Sprintf("Certificate installed to %s and trust store updated.", dest),
}
}

return InstallResult{
Message: fmt.Sprintf("Could not install cert system-wide (%s). Install manually.", firstErr),
Command: fmt.Sprintf("sudo cp %s /usr/local/share/ca-certificates/mitmdf-root-ca.crt && sudo update-ca-certificates", certPath),
}
}

func joinArgs(args []string) string {
s := ""
for i, a := range args {
if i > 0 {
s += " "
}
s += a
}
return s
}
Loading