generate self-signed certs on the go #1636

This commit is contained in:
Alireza Ahmadi 2026-08-09 18:54:48 +02:00
parent 2111bf0c5e
commit 767abf52ef
10 changed files with 96 additions and 0 deletions

View file

@ -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},

View file

@ -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")

View file

@ -104,6 +104,10 @@
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label=" ">
<a-button type="primary" icon="safety-certificate" @click="getNewSelfSignedCert(index)">{{ i18n
"pages.inbounds.generateSelfSignedCert" }}</a-button>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tls.ocspStapling" }}'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>

View file

@ -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;

View file

@ -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")

View file

@ -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"

View file

@ -181,6 +181,7 @@
"email" = "ایمیل"
"emailDesc" = "باید یک ایمیل یکتا باشد"
"setDefaultCert" = "استفاده از گواهی پنل"
"generateSelfSignedCert" = "ساخت گواهی خودامضا"
"telegramDesc" = "دریافت کنید‌ '/id'یا دستور @userinfobot آی‌دی‌(های) چت مدیر را بدون '@' واردکنید. از"
"subscriptionDesc" = "لینک سابسکربپشن خودرا در 'اطلاعات بیشتر' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید"
"info" = "اطلاعات"

View file

@ -181,6 +181,7 @@
"email" = "Email"
"emailDesc" = "Пожалуйста, укажите уникальный Email"
"setDefaultCert" = "Установить сертификат с панели"
"generateSelfSignedCert" = "Создать самоподписанный сертификат"
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких клиенты"
"info" = "Информация"

View file

@ -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"

View file

@ -181,6 +181,7 @@
"email" = "电子邮件"
"emailDesc" = "电子邮件必须完全唯一"
"setDefaultCert" = "从面板设置证书"
"generateSelfSignedCert" = "生成自签名证书"
"telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,你可以为多个客户端使用相同的名称"
"info" = "信息"