Files
wangjianhong 5e4e272b3a init
2025-07-23 17:30:33 +08:00

318 lines
8.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"ca-mini/db"
"ca-mini/internal/logger"
"ca-mini/internal/model"
"ca-mini/internal/repository"
"ca-mini/pkg/utils"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/gorilla/mux"
)
const (
// 错误码
ERROR_CODE_SUCCESS = 0
ERROR_CODE_FAIL = 1
// 错误信息
ERROR_MSG_SUCCESS = "success"
ERROR_MSG_FAIL = "fail"
WORK_PATH = "/opt/arrokoth/ca-mini"
CA_CERT_PATH = WORK_PATH + "/ca/CaRoot.crt"
CA_CONFIG_PATH = WORK_PATH + "/ca/ca.conf"
CRL_FILE_PATH = WORK_PATH + "/crl/CaRoot.crl"
)
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Date string `json:"date"`
}
type SuccessResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Date string `json:"date"`
}
type CertificateResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Date string `json:"date"`
Data model.CertInfo `json:"data"`
}
// IssueCertificate 处理证书签发请求
func IssueCertificate(w http.ResponseWriter, r *http.Request) {
// 解析CSR请求
var csrRequest struct {
Subject string `json:"subject"`
SubjectAltName string `json:"subjectAltName"`
Alg string `json:"alg"`
Len int `json:"len"`
}
if err := json.NewDecoder(r.Body).Decode(&csrRequest); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 检查Subject是否为空
if csrRequest.Subject == "" {
http.Error(w, "Subject is required", http.StatusBadRequest)
return
}
// 生成密钥
id, err := utils.GenerateKey(csrRequest.Alg, csrRequest.Len)
if err != nil {
http.Error(w, "Failed to generate key", http.StatusInternalServerError)
return
}
var sans []string
if csrRequest.SubjectAltName != "" {
sans = strings.Split(strings.TrimSpace(csrRequest.SubjectAltName), ",")
}
// 如果SubjectAltName为空则使用Subject作为SAN
if len(sans) == 0 {
sans = []string{csrRequest.Subject}
}
// 生成CSR文件
id, err = utils.GenerateCsr(id, csrRequest.Subject, sans)
if err != nil {
http.Error(w, "Failed to generate CSR file", http.StatusInternalServerError)
return
}
// 生成证书
id, err = utils.GenerateCert(id, 0)
if err != nil {
http.Error(w, "Failed to generate certificate", http.StatusInternalServerError)
return
}
// 解析证书信息
certInfo, err := ParseCert(id)
if err != nil {
http.Error(w, "Failed to parse certificate", http.StatusInternalServerError)
return
}
// 新增证书到数据库
repository := repository.NewCertificateRepository(db.DB)
if err := repository.AddCert(certInfo); err != nil {
logger.Error("Failed to save certificate to database %v", err)
http.Error(w, "Failed to save certificate", http.StatusInternalServerError)
return
}
// 返回证书信息
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
response := CertificateResponse{
Code: http.StatusOK,
Message: "Certificate issued successfully",
Date: time.Now().Format(time.RFC3339),
Data: *certInfo,
}
json.NewEncoder(w).Encode(response)
}
// 查询证书
func GetCertificate(w http.ResponseWriter, r *http.Request) {
// 从URL查询参数中获取id
//id := r.URL.Query().Get("id")
// 获取路径参数 id
vars := mux.Vars(r)
id := vars["id"]
if id == "" {
http.Error(w, "id is required", http.StatusBadRequest)
return
}
// 新增证书到数据库
repository := repository.NewCertificateRepository(db.DB)
certInfo, err := repository.SelectCertById(id)
if err != nil {
logger.Error("Failed to query certificate to database %v", err)
http.Error(w, "Failed to query certificate", http.StatusInternalServerError)
return
}
// 读取CA证书文件内容
caData, err := utils.GetCaCert()
if err == nil {
certInfo.Ca = caData
}
// 返回证书信息
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
response := CertificateResponse{
Code: http.StatusOK,
Message: "Certificate retrieved successfully",
Date: time.Now().Format(time.RFC3339),
Data: *certInfo,
}
json.NewEncoder(w).Encode(response)
}
// CheckBlacklist 处理黑名单查询请求
func CheckBlacklist(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{
"isBlacklisted": false,
})
}
// RevokeCertificate 处理证书撤销请求
func RevokeCertificate(w http.ResponseWriter, r *http.Request) {
// 从URL查询参数中获取id
// id := r.URL.Query().Get("id")
// 获取路径参数 id
vars := mux.Vars(r)
id := vars["id"]
if id == "" {
http.Error(w, "id is required", http.StatusBadRequest)
return
}
// 检查管理员权限假设通过HTTP Basic Auth验证
username, password, ok := r.BasicAuth()
if !ok || username != "admin" || password != "admin" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 解析证书信息
id, err := utils.RevokeCert(id)
if err != nil {
http.Error(w, "Failed to revoke certificate", http.StatusInternalServerError)
return
}
// 返回证书信息
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
response := SuccessResponse{
Code: http.StatusOK,
Message: id,
Date: time.Now().Format(time.RFC3339),
}
json.NewEncoder(w).Encode(response)
}
// 解析证书
func ParseCert(id string) (*model.CertInfo, error) {
// 读取私钥文件内容
keyData, err := utils.GetKey(id)
if err != nil {
return nil, fmt.Errorf("获取私钥失败: %v", err)
}
// 读取证书请求文件内容
csrData, err := utils.GetCsr(id)
if err != nil {
return nil, fmt.Errorf("获取证书请求失败: %v", err)
}
// 读取CA证书文件内容
caData, err := utils.GetCaCert()
if err != nil {
return nil, fmt.Errorf("获取CA证书失败: %v", err)
}
// 读取证书文件内容
certData, err := utils.GetCert(id)
if err != nil {
return nil, fmt.Errorf("获取证书失败: %v", err)
}
// 解析PEM编码的证书
block, _ := pem.Decode([]byte(certData))
if block == nil {
return nil, fmt.Errorf("无法解析PEM块")
}
// 解析X.509证书
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("解析证书失败: %v", err)
}
version := fmt.Sprintf("%d", cert.Version)
certCn := cert.Subject.CommonName
certDn := cert.Subject.String()
issuerCn := cert.Issuer.CommonName
issuerDn := cert.Issuer.String()
publicKeyAlg := cert.PublicKeyAlgorithm.String()
signatureAlg := cert.SignatureAlgorithm.String()
certSubAltName := ""
for i, DNSName := range cert.DNSNames {
if i == 0 {
certSubAltName = DNSName
} else {
certSubAltName = fmt.Sprintf("%s,%s", certSubAltName, DNSName)
}
}
// beforeTime := cert.NotBefore.Format(time.RFC3339)
// afterTime := cert.NotAfter.Format(time.RFC3339)
beforeTime := cert.NotBefore
afterTime := cert.NotAfter
serialNumber := cert.SerialNumber.String()
return &model.CertInfo{
CertId: id,
Version: version,
CertCn: certCn,
CertDn: certDn,
PublicKeyAlg: publicKeyAlg,
SignatureAlg: signatureAlg,
IssuerCn: issuerCn,
IssuerDn: issuerDn,
SerialNumber: serialNumber,
CertSubAltName: certSubAltName,
BeforeTime: beforeTime,
AfterTime: afterTime,
PrivateKey: keyData,
Csr: csrData,
Cert: certData,
Ca: caData,
}, nil
}
// DownloadCRL 处理黑名单文件下载请求
func DownloadCRL(w http.ResponseWriter, r *http.Request) {
// 打开黑名单文件
filePath := CRL_FILE_PATH
file, err := os.Open(filePath)
if err != nil {
http.Error(w, "Failed to open blacklist file", http.StatusInternalServerError)
return
}
defer file.Close()
// 设置响应头,触发浏览器下载行为
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=CaRoot.crl")
// 将文件内容写入响应体
if _, err := io.Copy(w, file); err != nil {
http.Error(w, "Error streaming the file", http.StatusInternalServerError)
return
}
}