110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package licensecheck
|
||
|
||
import (
|
||
"crypto/ecdsa"
|
||
"crypto/sha256"
|
||
"crypto/x509"
|
||
"encoding/asn1"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"encoding/pem"
|
||
"errors"
|
||
"math/big"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"intent-system/internal/hostid" // 你的 GetRootDiskSerial 包
|
||
)
|
||
|
||
/* ---------- ① 常量:内置公钥 ---------- */
|
||
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKQtVKlXjOro4qPoavqOU8m5qda0k
|
||
olAhHgTayzNuR+nOP1AnjQm10ehhV7Aafo8fUnjxs/rAM+MRdmzcEXrWnw==
|
||
-----END PUBLIC KEY-----`
|
||
|
||
/* ---------- ② 与服务端保持一致的结构 ---------- */
|
||
type licenseRequest struct {
|
||
MachineID string `json:"machine_id"`
|
||
Expiry string `json:"expiry"`
|
||
Features []string `json:"features"`
|
||
}
|
||
|
||
/* ---------- ③ 入口:Validate(path) ---------- */
|
||
func Validate(licPath string) error {
|
||
raw, err := os.ReadFile(licPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
parts := strings.Split(strings.TrimSpace(string(raw)), ".")
|
||
if len(parts) != 2 {
|
||
return errors.New("invalid license format")
|
||
}
|
||
payloadB64, sigB64 := parts[0], parts[1]
|
||
|
||
payload, err := base64.StdEncoding.DecodeString(payloadB64)
|
||
if err != nil {
|
||
return errors.New("payload base64 decode failed")
|
||
}
|
||
|
||
// 1. 验签
|
||
pubKey, err := parsePublicKey(publicKeyPEM)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !verifySignature(pubKey, payload, sigB64) {
|
||
return errors.New("signature mismatch")
|
||
}
|
||
|
||
// 2. 解析字段
|
||
var req licenseRequest
|
||
if err := json.Unmarshal(payload, &req); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 3. 校验机身号
|
||
sn, err := hostid.GetRootDiskSerial()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if req.MachineID != sn {
|
||
return errors.New("machine ID mismatch")
|
||
}
|
||
|
||
// 4. 校验过期
|
||
expiry, err := time.Parse("2006-01-02", req.Expiry)
|
||
if err != nil || time.Now().After(expiry) {
|
||
return errors.New("license expired")
|
||
}
|
||
|
||
return nil // 👍 通过
|
||
}
|
||
|
||
/* ---------- ④ 辅助函数 ---------- */
|
||
func parsePublicKey(pemStr string) (*ecdsa.PublicKey, error) {
|
||
block, _ := pem.Decode([]byte(pemStr))
|
||
if block == nil || block.Type != "PUBLIC KEY" {
|
||
return nil, errors.New("invalid PEM")
|
||
}
|
||
pubAny, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return pubAny.(*ecdsa.PublicKey), nil
|
||
}
|
||
|
||
type ecdsaSig struct{ R, S *big.Int }
|
||
|
||
func verifySignature(pub *ecdsa.PublicKey, msg []byte, sigB64 string) bool {
|
||
sigBytes, err := base64.StdEncoding.DecodeString(sigB64)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
var sig ecdsaSig
|
||
if _, err = asn1.Unmarshal(sigBytes, &sig); err != nil {
|
||
return false
|
||
}
|
||
hash := sha256.Sum256(msg)
|
||
return ecdsa.Verify(pub, hash[:], sig.R, sig.S)
|
||
}
|