318 lines
8.1 KiB
Go
318 lines
8.1 KiB
Go
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
|
||
}
|
||
}
|