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" = "信息"