golang实现kms

go 复制代码
package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"math/big"
	"mime/multipart"
	"net/http"
	"time"
)

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	api := r.Group("/api")
	{
		api.POST("/generate-ca", generateCA)
		api.POST("/sign-cert", signCert)
		api.POST("/encrypt", encrypt)
		api.POST("/decrypt", decrypt)
		api.POST("/sign", sign)
		api.POST("/verify", verify)
		api.POST("/generate-keypair", generateKeypair)
	}

	r.Run(":8080")
}

// 生成 CA 证书
func generateCA(c *gin.Context) {
	var req struct {
		CommonName         string `form:"common_name" binding:"required"`
		Days               int    `form:"days" binding:"required,min=1"`
		Country            string `form:"country"`
		Province           string `form:"province"`
		Locality           string `form:"locality"`
		Organization       string `form:"organization"`
		OrganizationalUnit string `form:"organizational_unit"`
		KeyBits            int    `form:"key_bits" binding:"required,oneof=2048 4096"`
	}
	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 生成 RSA 密钥对
	privateKey, err := rsa.GenerateKey(rand.Reader, req.KeyBits)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "生成私钥失败"})
		return
	}

	// 构造证书模板
	template := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano()),
		Subject: pkix.Name{
			CommonName:         req.CommonName,
			Country:            []string{req.Country},
			Province:           []string{req.Province},
			Locality:           []string{req.Locality},
			Organization:       []string{req.Organization},
			OrganizationalUnit: []string{req.OrganizationalUnit},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(0, 0, req.Days),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	// 自签名
	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "创建证书失败"})
		return
	}

	// 编码为 PEM
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
	privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})

	// 提取公钥
	pubASN1, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "编码公钥失败"})
		return
	}
	pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})

	c.JSON(http.StatusOK, gin.H{
		"ca_cert":        string(certPEM),
		"ca_private_key": string(privPEM),
		"ca_public_key":  string(pubPEM), // 新增公钥
	})
}

// 签发终端证书
func signCert(c *gin.Context) {
	// 获取上传的 CA 证书和私钥
	caCertFile, err := c.FormFile("ca_cert")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 CA 证书文件"})
		return
	}
	caKeyFile, err := c.FormFile("ca_private_key")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 CA 私钥文件"})
		return
	}

	// 读取文件内容
	caCertBytes, err := readFileBytes(caCertFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取 CA 证书失败"})
		return
	}
	caKeyBytes, err := readFileBytes(caKeyFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取 CA 私钥失败"})
		return
	}

	// 解析 CA 证书和私钥
	caCertBlock, _ := pem.Decode(caCertBytes)
	if caCertBlock == nil || caCertBlock.Type != "CERTIFICATE" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 CA 证书 PEM"})
		return
	}
	caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析 CA 证书失败"})
		return
	}

	caKeyBlock, _ := pem.Decode(caKeyBytes)
	if caKeyBlock == nil || (caKeyBlock.Type != "RSA PRIVATE KEY" && caKeyBlock.Type != "PRIVATE KEY") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 CA 私钥 PEM"})
		return
	}
	var caPrivateKey interface{}
	if caKeyBlock.Type == "RSA PRIVATE KEY" {
		caPrivateKey, err = x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes)
	} else {
		caPrivateKey, err = x509.ParsePKCS8PrivateKey(caKeyBlock.Bytes)
	}
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析 CA 私钥失败"})
		return
	}
	caRsaPrivateKey, ok := caPrivateKey.(*rsa.PrivateKey)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "CA 私钥不是 RSA 密钥"})
		return
	}

	// 获取证书参数
	commonName := c.PostForm("common_name")
	days := c.PostForm("days")
	country := c.PostForm("country")
	province := c.PostForm("province")
	locality := c.PostForm("locality")
	organization := c.PostForm("organization")
	organizationalUnit := c.PostForm("organizational_unit")
	keyBits := c.PostForm("key_bits")
	if commonName == "" || days == "" || keyBits == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少必要参数 (common_name, days, key_bits)"})
		return
	}
	daysInt := atoi(days, 365)
	keyBitsInt := atoi(keyBits, 2048)

	// 生成新证书的密钥对
	certPrivateKey, err := rsa.GenerateKey(rand.Reader, keyBitsInt)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "生成证书私钥失败"})
		return
	}

	// 证书模板
	template := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano()),
		Subject: pkix.Name{
			CommonName:         commonName,
			Country:            []string{country},
			Province:           []string{province},
			Locality:           []string{locality},
			Organization:       []string{organization},
			OrganizationalUnit: []string{organizationalUnit},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(0, 0, daysInt),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  false,
	}

	// 使用 CA 签发新证书
	certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &certPrivateKey.PublicKey, caRsaPrivateKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "签发证书失败"})
		return
	}

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
	privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(certPrivateKey)})

	// 提取公钥
	pubASN1, err := x509.MarshalPKIXPublicKey(&certPrivateKey.PublicKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "编码公钥失败"})
		return
	}
	pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})

	c.JSON(http.StatusOK, gin.H{
		"sub_cert":        string(certPEM),
		"sub_private_key": string(privPEM),
		"sub_public_key":  string(pubPEM), // 新增公钥
	})
}

// 加密
func encrypt(c *gin.Context) {
	pubKeyFile, err := c.FormFile("public_key")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少公钥文件"})
		return
	}
	message := c.PostForm("message")
	if message == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少消息"})
		return
	}

	pubKeyBytes, err := readFileBytes(pubKeyFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取公钥失败"})
		return
	}
	pubKeyBlock, _ := pem.Decode(pubKeyBytes)
	if pubKeyBlock == nil || (pubKeyBlock.Type != "PUBLIC KEY" && pubKeyBlock.Type != "RSA PUBLIC KEY") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公钥 PEM"})
		return
	}
	var pubKey interface{}
	if pubKeyBlock.Type == "RSA PUBLIC KEY" {
		pubKey, err = x509.ParsePKCS1PublicKey(pubKeyBlock.Bytes)
	} else {
		pubKey, err = x509.ParsePKIXPublicKey(pubKeyBlock.Bytes)
	}
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析公钥失败"})
		return
	}
	rsaPubKey, ok := pubKey.(*rsa.PublicKey)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "公钥不是 RSA 公钥"})
		return
	}

	ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPubKey, []byte(message), nil)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "加密失败: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"encrypted": base64.StdEncoding.EncodeToString(ciphertext),
	})
}

// 解密
func decrypt(c *gin.Context) {
	privKeyFile, err := c.FormFile("private_key")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少私钥文件"})
		return
	}
	encryptedB64 := c.PostForm("encrypted")
	if encryptedB64 == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少加密数据"})
		return
	}
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedB64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 base64 编码"})
		return
	}

	privKeyBytes, err := readFileBytes(privKeyFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取私钥失败"})
		return
	}
	privKeyBlock, _ := pem.Decode(privKeyBytes)
	if privKeyBlock == nil || (privKeyBlock.Type != "RSA PRIVATE KEY" && privKeyBlock.Type != "PRIVATE KEY") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的私钥 PEM"})
		return
	}
	var privKey interface{}
	if privKeyBlock.Type == "RSA PRIVATE KEY" {
		privKey, err = x509.ParsePKCS1PrivateKey(privKeyBlock.Bytes)
	} else {
		privKey, err = x509.ParsePKCS8PrivateKey(privKeyBlock.Bytes)
	}
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析私钥失败"})
		return
	}
	rsaPrivKey, ok := privKey.(*rsa.PrivateKey)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "私钥不是 RSA 私钥"})
		return
	}

	plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, rsaPrivKey, ciphertext, nil)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "解密失败: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": string(plaintext),
	})
}

// 签名
func sign(c *gin.Context) {
	privKeyFile, err := c.FormFile("private_key")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少私钥文件"})
		return
	}
	message := c.PostForm("message")
	if message == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少消息"})
		return
	}

	privKeyBytes, err := readFileBytes(privKeyFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取私钥失败"})
		return
	}
	privKeyBlock, _ := pem.Decode(privKeyBytes)
	if privKeyBlock == nil || (privKeyBlock.Type != "RSA PRIVATE KEY" && privKeyBlock.Type != "PRIVATE KEY") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的私钥 PEM"})
		return
	}
	var privKey interface{}
	if privKeyBlock.Type == "RSA PRIVATE KEY" {
		privKey, err = x509.ParsePKCS1PrivateKey(privKeyBlock.Bytes)
	} else {
		privKey, err = x509.ParsePKCS8PrivateKey(privKeyBlock.Bytes)
	}
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析私钥失败"})
		return
	}
	rsaPrivKey, ok := privKey.(*rsa.PrivateKey)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "私钥不是 RSA 私钥"})
		return
	}

	hashed := sha256.Sum256([]byte(message))
	signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, crypto.SHA256, hashed[:])
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "签名失败: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"signature": base64.StdEncoding.EncodeToString(signature),
	})
}

// 验签
func verify(c *gin.Context) {
	pubKeyFile, err := c.FormFile("public_key")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少公钥文件"})
		return
	}
	message := c.PostForm("message")
	signatureB64 := c.PostForm("signature")
	if message == "" || signatureB64 == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "缺少消息或签名"})
		return
	}
	signature, err := base64.StdEncoding.DecodeString(signatureB64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 base64 签名"})
		return
	}

	pubKeyBytes, err := readFileBytes(pubKeyFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "读取公钥失败"})
		return
	}
	pubKeyBlock, _ := pem.Decode(pubKeyBytes)
	if pubKeyBlock == nil || (pubKeyBlock.Type != "PUBLIC KEY" && pubKeyBlock.Type != "RSA PUBLIC KEY") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公钥 PEM"})
		return
	}
	var pubKey interface{}
	if pubKeyBlock.Type == "RSA PUBLIC KEY" {
		pubKey, err = x509.ParsePKCS1PublicKey(pubKeyBlock.Bytes)
	} else {
		pubKey, err = x509.ParsePKIXPublicKey(pubKeyBlock.Bytes)
	}
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "解析公钥失败"})
		return
	}
	rsaPubKey, ok := pubKey.(*rsa.PublicKey)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "公钥不是 RSA 公钥"})
		return
	}

	hashed := sha256.Sum256([]byte(message))
	err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], signature)
	valid := err == nil

	c.JSON(http.StatusOK, gin.H{
		"valid": valid,
	})
}

// 生成 RSA 密钥对
func generateKeypair(c *gin.Context) {
	var req struct {
		KeyBits int `form:"key_bits" binding:"required,oneof=2048 4096"`
	}
	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	privateKey, err := rsa.GenerateKey(rand.Reader, req.KeyBits)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "生成私钥失败"})
		return
	}

	privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
	pubASN1, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "编码公钥失败"})
		return
	}
	pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})

	c.JSON(http.StatusOK, gin.H{
		"private_key": string(privPEM),
		"public_key":  string(pubPEM),
	})
}

// 辅助函数:从上传的文件头读取所有字节
func readFileBytes(fileHeader *multipart.FileHeader) ([]byte, error) {
	file, err := fileHeader.Open()
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return io.ReadAll(file)
}

// 简单字符串转整数
func atoi(s string, defaultValue int) int {
	var v int
	fmt.Sscanf(s, "%d", &v)
	if v == 0 {
		return defaultValue
	}
	return v
}
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>CA 工具包</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .tabs { overflow: hidden; border-bottom: 1px solid #ccc; }
        .tab-button { background-color: #f1f1f1; float: left; border: none; outline: none; cursor: pointer; padding: 10px 20px; transition: 0.3s; }
        .tab-button:hover { background-color: #ddd; }
        .tab-button.active { background-color: #ccc; }
        .tab-content { display: none; padding: 20px; border: 1px solid #ccc; border-top: none; }
        .tab-content.active { display: block; }
        label { display: inline-block; width: 150px; margin: 5px 0; }
        input[type=text], input[type=number], textarea { width: 300px; padding: 5px; }
        textarea { height: 100px; font-family: monospace; }
        button { margin: 10px 0; padding: 8px 15px; }
        .result { margin-top: 15px; padding: 10px; background: #f5f5f5; border-radius: 5px; }
        .error { color: red; }
        .section { border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; }
        .download-btn {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 5px 10px;
            margin: 2px 5px 2px 0;
            cursor: pointer;
            border-radius: 3px;
        }
        .download-btn:hover { background-color: #45a049; }
        pre { background: #fff; padding: 10px; border: 1px solid #ddd; overflow: auto; }
    </style>
</head>
<body>
<h1>CA 工具包</h1>
<div class="tabs">
    <button class="tab-button active" onclick="openTab(event, 'ca')">生成 CA</button>
    <button class="tab-button" onclick="openTab(event, 'cert')">签发证书</button>
    <button class="tab-button" onclick="openTab(event, 'crypto')">加密/解密</button>
    <button class="tab-button" onclick="openTab(event, 'sign')">签名/验签</button>
    <button class="tab-button" onclick="openTab(event, 'keygen')">生成密钥对</button>
</div>

<!-- 生成 CA -->
<div id="ca" class="tab-content active">
    <h2>生成自签名 CA 证书</h2>
    <form id="caForm">
        <label>通用名称 (CN):</label> <input type="text" name="common_name" required><br>
        <label>有效期 (天):</label> <input type="number" name="days" value="365" min="1" required><br>
        <label>国家 (C):</label> <input type="text" name="country" value="CN"><br>
        <label>省份 (ST):</label> <input type="text" name="province" value="Beijing"><br>
        <label>城市 (L):</label> <input type="text" name="locality" value="Beijing"><br>
        <label>组织 (O):</label> <input type="text" name="organization" value="MyOrg"><br>
        <label>组织单位 (OU):</label> <input type="text" name="organizational_unit" value="IT"><br>
        <label>密钥位数:</label>
        <select name="key_bits">
            <option value="2048">2048</option>
            <option value="4096">4096</option>
        </select><br>
        <button type="button" onclick="submitCA()">生成 CA</button>
    </form>
    <div id="caResult" class="result"></div>
</div>

<!-- 签发证书 -->
<div id="cert" class="tab-content">
    <h2>使用 CA 签发终端证书</h2>
    <form id="certForm" enctype="multipart/form-data">
        <label>CA 证书 (PEM):</label> <input type="file" name="ca_cert" accept=".crt,.pem" required><br>
        <label>CA 私钥 (PEM):</label> <input type="file" name="ca_private_key" accept=".key,.pem" required><br>
        <label>通用名称 (CN):</label> <input type="text" name="common_name" required><br>
        <label>有效期 (天):</label> <input type="number" name="days" value="365" min="1" required><br>
        <label>国家 (C):</label> <input type="text" name="country"><br>
        <label>省份 (ST):</label> <input type="text" name="province"><br>
        <label>城市 (L):</label> <input type="text" name="locality"><br>
        <label>组织 (O):</label> <input type="text" name="organization"><br>
        <label>组织单位 (OU):</label> <input type="text" name="organizational_unit"><br>
        <label>密钥位数:</label>
        <select name="key_bits">
            <option value="2048">2048</option>
            <option value="4096">4096</option>
        </select><br>
        <button type="button" onclick="submitCert()">签发证书</button>
    </form>
    <div id="certResult" class="result"></div>
</div>

<!-- 加密/解密 -->
<div id="crypto" class="tab-content">
    <div class="section">
        <h3>加密</h3>
        <form id="encryptForm" enctype="multipart/form-data">
            <label>公钥 (PEM):</label> <input type="file" name="public_key" accept=".pem,.pub" required><br>
            <label>消息:</label> <input type="text" name="message" size="50" required><br>
            <button type="button" onclick="submitEncrypt()">加密</button>
        </form>
        <div id="encryptResult" class="result"></div>
    </div>
    <div class="section">
        <h3>解密</h3>
        <form id="decryptForm" enctype="multipart/form-data">
            <label>私钥 (PEM):</label> <input type="file" name="private_key" accept=".key,.pem" required><br>
            <label>加密数据 (base64):</label> <textarea name="encrypted" required></textarea><br>
            <button type="button" onclick="submitDecrypt()">解密</button>
        </form>
        <div id="decryptResult" class="result"></div>
    </div>
</div>

<!-- 签名/验签 -->
<div id="sign" class="tab-content">
    <div class="section">
        <h3>签名</h3>
        <form id="signForm" enctype="multipart/form-data">
            <label>私钥 (PEM):</label> <input type="file" name="private_key" accept=".key,.pem" required><br>
            <label>消息:</label> <input type="text" name="message" size="50" required><br>
            <button type="button" onclick="submitSign()">签名</button>
        </form>
        <div id="signResult" class="result"></div>
    </div>
    <div class="section">
        <h3>验签</h3>
        <form id="verifyForm" enctype="multipart/form-data">
            <label>公钥 (PEM):</label> <input type="file" name="public_key" accept=".pem,.pub" required><br>
            <label>消息:</label> <input type="text" name="message" size="50" required><br>
            <label>签名 (base64):</label> <textarea name="signature" required></textarea><br>
            <button type="button" onclick="submitVerify()">验签</button>
        </form>
        <div id="verifyResult" class="result"></div>
    </div>
</div>

<!-- 生成密钥对 -->
<div id="keygen" class="tab-content">
    <h2>生成 RSA 密钥对</h2>
    <form id="keygenForm">
        <label>密钥位数:</label>
        <select name="key_bits">
            <option value="2048">2048</option>
            <option value="4096">4096</option>
        </select><br>
        <button type="button" onclick="submitKeygen()">生成</button>
    </form>
    <div id="keygenResult" class="result"></div>
</div>

<script>
    function openTab(evt, tabName) {
        var i, tabcontent, tabbuttons;
        tabcontent = document.getElementsByClassName("tab-content");
        for (i = 0; i < tabcontent.length; i++) {
            tabcontent[i].classList.remove("active");
        }
        tabbuttons = document.getElementsByClassName("tab-button");
        for (i = 0; i < tabbuttons.length; i++) {
            tabbuttons[i].classList.remove("active");
        }
        document.getElementById(tabName).classList.add("active");
        evt.currentTarget.classList.add("active");
    }

    async function postForm(formId, url, resultId) {
        const form = document.getElementById(formId);
        const formData = new FormData(form);
        const response = await fetch(url, {
            method: 'POST',
            body: formData
        });
        const data = await response.json();
        const resultDiv = document.getElementById(resultId);
        // 清空之前的内容
        resultDiv.innerHTML = '';
        if (response.ok) {
            // 显示 JSON 预览
            const pre = document.createElement('pre');
            pre.textContent = JSON.stringify(data, null, 2);
            resultDiv.appendChild(pre);

            // 检测 PEM 字段并提供下载按钮
            const downloadDiv = document.createElement('div');
            downloadDiv.innerHTML = '<h4>下载 PEM 文件:</h4>';
            let hasPem = false;
            for (const [key, value] of Object.entries(data)) {
                if (typeof value === 'string' && value.trim().startsWith('-----BEGIN')) {
                    hasPem = true;
                    const btn = document.createElement('button');
                    btn.className = 'download-btn';
                    btn.textContent = `下载 ${key}.pem`;
                    btn.onclick = function() {
                        const blob = new Blob([value], { type: 'application/x-pem-file' });
                        const link = document.createElement('a');
                        link.href = URL.createObjectURL(blob);
                        link.download = key + '.pem';
                        link.click();
                        URL.revokeObjectURL(link.href);
                    };
                    downloadDiv.appendChild(btn);
                    downloadDiv.appendChild(document.createElement('br'));
                }
            }
            if (hasPem) {
                resultDiv.appendChild(downloadDiv);
            }
            resultDiv.classList.remove('error');
        } else {
            resultDiv.innerHTML = '<span class="error">错误: ' + (data.error || '未知错误') + '</span>';
            resultDiv.classList.add('error');
        }
    }

    function submitCA() { postForm('caForm', '/api/generate-ca', 'caResult'); }
    function submitCert() { postForm('certForm', '/api/sign-cert', 'certResult'); }
    function submitEncrypt() { postForm('encryptForm', '/api/encrypt', 'encryptResult'); }
    function submitDecrypt() { postForm('decryptForm', '/api/decrypt', 'decryptResult'); }
    function submitSign() { postForm('signForm', '/api/sign', 'signResult'); }
    function submitVerify() { postForm('verifyForm', '/api/verify', 'verifyResult'); }
    function submitKeygen() { postForm('keygenForm', '/api/generate-keypair', 'keygenResult'); }
</script>
</body>
</html>
相关推荐
小邓睡不饱耶2 小时前
基于Python的Q房网二手房数据爬虫实现
开发语言·爬虫·python
深蓝电商API2 小时前
爬虫任务调度:APScheduler 定时执行
开发语言·爬虫·python
kang_jin2 小时前
超详细 Python 爬虫指南
开发语言·爬虫·python
Sylvia-girl2 小时前
C语言-1入门
c语言·开发语言
Rust语言中文社区2 小时前
【Rust日报】 CEL与Rust实现接近原生速度的解释执行
开发语言·后端·rust
C+++Python2 小时前
C++ 策略模式实战:从原理到落地
开发语言·c++·策略模式
IT北辰2 小时前
不规则 Excel“数据提取——教师课表自动汇总实战
开发语言·爬虫·python
勿芮介2 小时前
【研发工具】OpenClaw基础环境安装全教程-Node\NVM\PNPM\Bash
开发语言·node.js·bash·ai编程
JamesYoung79712 小时前
第七部分 — 存储 数据建模与迁移提示
java·开发语言·数据结构