From 767abf52ef73f54a0b4e9ed05c7aa95bbc329e99 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Sun, 9 Aug 2026 18:54:48 +0200 Subject: [PATCH] generate self-signed certs on the go #1636 --- web/controller/api.go | 1 + web/controller/server.go | 11 +++++ web/html/xui/form/tls_settings.html | 4 ++ web/html/xui/inbound_modal.html | 13 ++++++ web/network/tls.go | 62 ++++++++++++++++++++++++++ web/translation/translate.en_US.toml | 1 + web/translation/translate.fa_IR.toml | 1 + web/translation/translate.ru_RU.toml | 1 + web/translation/translate.vi_VN.toml | 1 + web/translation/translate.zh_Hans.toml | 1 + 10 files changed, 96 insertions(+) diff --git a/web/controller/api.go b/web/controller/api.go index 1f0fa440..696d8eac 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -132,6 +132,7 @@ func (a *APIController) serverApi(api *gin.RouterGroup) { {"GET", "/getNewmldsa65", a.serverController.getNewmldsa65}, {"POST", "/getNewEchCert", a.serverController.getNewEchCert}, + {"POST", "/getNewSelfSignedCert", a.serverController.getNewSelfSignedCert}, {"POST", "/getCertHash", a.serverController.getCertHash}, {"POST", "/getTlsPing", a.serverController.getTlsPing}, {"POST", "/importDB", a.serverController.importDB}, diff --git a/web/controller/server.go b/web/controller/server.go index e18eb366..c338ab13 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -49,6 +49,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.GET("/getNewX25519Cert", a.getNewX25519Cert) g.POST("/getNewEchCert", a.getNewEchCert) + g.POST("/getNewSelfSignedCert", a.getNewSelfSignedCert) g.POST("/getCertHash", a.getCertHash) g.POST("/getTlsPing", a.getTlsPing) g.POST("/stopXrayService", a.stopXrayService) @@ -213,6 +214,16 @@ func (a *ServerController) getNewEchCert(c *gin.Context) { jsonObj(c, cert, nil) } +func (a *ServerController) getNewSelfSignedCert(c *gin.Context) { + sni := c.PostForm("sni") + cert, err := network.GenerateSelfSignedCert(sni) + if err != nil { + jsonMsg(c, "generate self-signed certificate", err) + return + } + jsonObj(c, cert, nil) +} + func (a *ServerController) getCertHash(c *gin.Context) { certFile := c.PostForm("certFile") certContent := c.PostForm("certContent") diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html index 2c556541..b6d50327 100644 --- a/web/html/xui/form/tls_settings.html +++ b/web/html/xui/form/tls_settings.html @@ -104,6 +104,10 @@ + + {{ i18n + "pages.inbounds.generateSelfSignedCert" }} + diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index c1723b00..460f573e 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -251,6 +251,19 @@ this.inbound.stream.tls.echServerKeys = ''; this.inbound.stream.tls.settings.echConfigList = ''; }, + async getNewSelfSignedCert(index) { + const cert = inModal.inbound.stream.tls.certs[index]; + if (!cert) return; + inModal.loading(true); + const msg = await HttpUtil.post('/server/getNewSelfSignedCert', {sni: inModal.inbound.stream.tls.sni}); + inModal.loading(false); + if (!msg.success) { + return; + } + cert.useFile = false; + cert.cert = msg.obj.cert; + cert.key = msg.obj.key; + }, async getCertHash() { const cert = inModal.inbound.stream.tls.certs[0]; if (!cert) return; diff --git a/web/network/tls.go b/web/network/tls.go index 95177d2f..2e30c500 100644 --- a/web/network/tls.go +++ b/web/network/tls.go @@ -2,11 +2,16 @@ package network import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "encoding/hex" "encoding/pem" + "math/big" "net" "os" "strings" @@ -80,6 +85,63 @@ func GetCertHash(certFile string, certContent string) ([]string, error) { return hashes, nil } +func GenerateSelfSignedCert(serverName string) (any, error) { + var names []string + for _, name := range strings.Split(serverName, ",") { + if name = strings.TrimSpace(name); name != "" { + names = append(names, name) + } + } + if len(names) == 0 { + names = []string{"localhost", "127.0.0.1", "::1"} + } + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, common.NewError("unable to generate private key: ", err) + } + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, common.NewError("unable to generate serial number: ", err) + } + + now := time.Now() + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: names[0]}, + NotBefore: now.Add(-24 * time.Hour), + NotAfter: now.AddDate(10, 0, 0), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + for _, name := range names { + if ip := net.ParseIP(name); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, name) + } + } + + certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, common.NewError("unable to create certificate: ", err) + } + keyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, common.NewError("unable to encode private key: ", err) + } + + certObj := map[string]string{ + "cert": string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})), + "key": string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})), + } + + return certObj, nil +} + func GetTlsPing(domain string, port string) (any, error) { if domain == "" { return "", common.NewError("domain is empty") diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 71c7696d..413d955e 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -181,6 +181,7 @@ "email" = "Email" "emailDesc" = "Please provide a unique email address" "setDefaultCert" = "Set Cert from Panel" +"generateSelfSignedCert" = "Generate Self-Signed Cert" "telegramDesc" = "Please provide Chat ID(s) without using the '@' symbol. (Get it here @userinfobot) or (Use '/id' command in the bot)" "subscriptionDesc" = "To find your subscription URL, navigate to the 'More Information'. Additionally, you can use the same name for several clients." "info" = "Info" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 02fe5230..36abf11d 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -181,6 +181,7 @@ "email" = "ایمیل" "emailDesc" = "باید یک ایمیل یکتا باشد" "setDefaultCert" = "استفاده از گواهی پنل" +"generateSelfSignedCert" = "ساخت گواهی خودامضا" "telegramDesc" = "دریافت کنید‌ '/id'یا دستور @userinfobot آی‌دی‌(های) چت مدیر را بدون '@' واردکنید. از" "subscriptionDesc" = "لینک سابسکربپشن خودرا در 'اطلاعات بیشتر' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید" "info" = "اطلاعات" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 591d5169..e6356baf 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -181,6 +181,7 @@ "email" = "Email" "emailDesc" = "Пожалуйста, укажите уникальный Email" "setDefaultCert" = "Установить сертификат с панели" +"generateSelfSignedCert" = "Создать самоподписанный сертификат" "telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)" "subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких клиенты" "info" = "Информация" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 751d95ba..9482f992 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -181,6 +181,7 @@ "email" = "Email" "emailDesc" = "vui lòng cung cấp một địa chỉ email duy nhất." "setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển" +"generateSelfSignedCert" = "Tạo chứng chỉ tự ký" "telegramDesc" = "Sử dụng ID Telegram không có @ hoặc ID trò chuyện (bạn có thể lấy nó tại đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)" "subscriptionDesc" = "Bạn có thể tìm thấy liên kết phụ của mình trên Chi tiết, bạn cũng có thể sử dụng cùng tên cho một số khách hàng" "info" = "Thông tin" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 4f98cbbb..85ae1efe 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -181,6 +181,7 @@ "email" = "电子邮件" "emailDesc" = "电子邮件必须完全唯一" "setDefaultCert" = "从面板设置证书" +"generateSelfSignedCert" = "生成自签名证书" "telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "subscriptionDesc" = "您可以在详细信息上找到您的子链接,你可以为多个客户端使用相同的名称" "info" = "信息"