182 lines
4.7 KiB
Go
182 lines
4.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"time"
|
|
|
|
"licensing-cotton/internal/database"
|
|
"licensing-cotton/internal/logger"
|
|
"licensing-cotton/internal/security"
|
|
)
|
|
|
|
// License 结构
|
|
type License struct {
|
|
DeviceID string `json:"device_id"`
|
|
Expiration string `json:"expiration"`
|
|
SignDate string `json:"sign_date"`
|
|
Signature string `json:"signature"`
|
|
}
|
|
|
|
type LicenseWithoutSign struct {
|
|
DeviceID string `json:"device_id"`
|
|
Expiration string `json:"expiration"`
|
|
SignDate string `json:"sign_date"`
|
|
}
|
|
|
|
// 签发License (仅管理员)
|
|
func HandleSignLicense(w http.ResponseWriter, r *http.Request) {
|
|
if !isAdminRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DeviceID string `json:"device_id"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "解析请求失败", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// 查设备
|
|
var expiration time.Time
|
|
err := database.DB.QueryRow(`SELECT expiration FROM devices WHERE device_id=?`, req.DeviceID).
|
|
Scan(&expiration)
|
|
if err != nil {
|
|
http.Error(w, "设备不存在", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// 构造 license
|
|
lic := License{
|
|
DeviceID: req.DeviceID,
|
|
Expiration: expiration.Format(time.RFC3339),
|
|
}
|
|
// 序列化并哈希
|
|
dataBytes, _ := json.Marshal(lic)
|
|
hash := sha256.Sum256(dataBytes)
|
|
|
|
// 签名
|
|
priv, _, _ := security.GetKeys()
|
|
rSig, sSig, err := ecdsa.Sign(nil, priv, hash[:])
|
|
if err != nil {
|
|
http.Error(w, "ECDSA签名失败", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
sigBytes := append(rSig.Bytes(), sSig.Bytes()...)
|
|
lic.Signature = base64.StdEncoding.EncodeToString(sigBytes)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(lic)
|
|
}
|
|
|
|
// 验证License (可供服务端或第三方调用)
|
|
func HandleVerifyLicense(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "只允许POST", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var lic License
|
|
if err := json.NewDecoder(r.Body).Decode(&lic); err != nil {
|
|
http.Error(w, "解析License失败", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
sigStr := lic.Signature
|
|
lic.Signature = ""
|
|
|
|
dataBytes, _ := json.Marshal(lic)
|
|
hash := sha256.Sum256(dataBytes)
|
|
|
|
sigData, err := base64.StdEncoding.DecodeString(sigStr)
|
|
if err != nil {
|
|
http.Error(w, "Signature base64解码失败", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
half := len(sigData) / 2
|
|
rSig := new(big.Int).SetBytes(sigData[:half])
|
|
sSig := new(big.Int).SetBytes(sigData[half:])
|
|
|
|
_, pub, _ := security.GetKeys()
|
|
if !ecdsa.Verify(pub, hash[:], rSig, sSig) {
|
|
http.Error(w, "签名验证失败", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// 检查是否过期
|
|
exp, err := time.Parse(time.RFC3339, lic.Expiration)
|
|
if err != nil {
|
|
http.Error(w, "时间格式错误", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if time.Now().After(exp) {
|
|
http.Error(w, "License已过期", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// 可进一步检查数据库中的device_id与expiration是否匹配
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"message": "License有效"}`))
|
|
}
|
|
|
|
func SignLicense(deviceID string, expirationTime time.Time) (License, error) {
|
|
// 1. 准备待签名数据
|
|
licWithoutSign := LicenseWithoutSign{
|
|
DeviceID: deviceID,
|
|
Expiration: expirationTime.Format(time.RFC3339),
|
|
SignDate: time.Now().Format(time.RFC3339),
|
|
}
|
|
|
|
// 2. 序列化为 JSON
|
|
licBytes, err := json.Marshal(licWithoutSign)
|
|
if err != nil {
|
|
return License{}, fmt.Errorf("序列化License失败: %w", err)
|
|
}
|
|
|
|
// 3. 调用你的 Ed25519Sign 函数签名
|
|
signature, signErr := security.Ed25519Sign(licBytes) // <-- 这里是你已有的签名函数
|
|
if signErr != nil {
|
|
return License{}, fmt.Errorf("签名失败: %w", signErr)
|
|
}
|
|
|
|
// 4. 封装最终 License
|
|
lic := License{
|
|
DeviceID: deviceID,
|
|
Expiration: licWithoutSign.Expiration,
|
|
SignDate: licWithoutSign.SignDate,
|
|
Signature: base64.StdEncoding.EncodeToString(signature),
|
|
}
|
|
logger.Info("签发了 License: device=%s, expiration=%s", deviceID, expirationTime.Format(time.RFC3339))
|
|
return lic, nil
|
|
}
|
|
|
|
// HandleGetPublicKey 获取 Ed25519 公钥
|
|
func HandleGetPublicKey(w http.ResponseWriter, r *http.Request) {
|
|
sshKey, err := security.GetEd25519PublicKeySSH()
|
|
if err != nil {
|
|
http.Error(w, "获取公钥失败: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
base64Key, err := security.GetEd25519PublicKeyBase64()
|
|
if err != nil {
|
|
http.Error(w, "获取公钥失败: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"ssh_format": sshKey,
|
|
"base64": base64Key,
|
|
"algorithm": "Ed25519",
|
|
"usage": "用于验证 License 签名",
|
|
})
|
|
}
|