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