From c37bd0908a19437fb1360d1a02c2b50b43171a0c Mon Sep 17 00:00:00 2001 From: nuknal Date: Thu, 12 Dec 2024 14:44:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=88=E6=9D=83=E9=AA=8C?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .vscode/settings.json | 5 + Makefile | 15 ++- cmd/main.go | 3 + go.sum | 2 + license/no_license.go | 10 ++ license/public/algorithm.go | 49 +++++++++ license/public/ecdsa.go | 149 +++++++++++++++++++++++++ license/public/errors.go | 62 +++++++++++ license/public/file.go | 120 +++++++++++++++++++++ license/public/key.go | 68 ++++++++++++ license/public/license.go | 167 ++++++++++++++++++++++++++++ license/public/machine.go | 203 +++++++++++++++++++++++++++++++++++ license/public/privatekey.go | 29 +++++ license/public/public.go | 100 +++++++++++++++++ license/public/publickey.go | 39 +++++++ license/public/version.go | 24 +++++ license/with_license.go | 53 +++++++++ 18 files changed, 1096 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 license/no_license.go create mode 100644 license/public/algorithm.go create mode 100644 license/public/ecdsa.go create mode 100644 license/public/errors.go create mode 100644 license/public/file.go create mode 100644 license/public/key.go create mode 100644 license/public/license.go create mode 100644 license/public/machine.go create mode 100644 license/public/privatekey.go create mode 100644 license/public/public.go create mode 100644 license/public/publickey.go create mode 100644 license/public/version.go create mode 100644 license/with_license.go diff --git a/.gitignore b/.gitignore index b90ad28..c17efc0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build/go *.tif *.tiff dem/* +license/keys diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..10df20f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "gopls": { + "build.buildFlags": ["-tags=local"] + } +} diff --git a/Makefile b/Makefile index 6bd347a..807b3fa 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ endif export COMMIT_CNT := $(shell git rev-list HEAD | wc -l | sed 's/ //g' ) export BUILD_NUMBER := ${BRANCH}-${COMMIT_CNT} +export PriKeyVAL := $(shell cat license/keys/v1/prikey.pem) + export COMPILE_LDFLAGS='-s -w \ -X "main.BuildDate=${DATE}" \ -X "main.LatestCommit=${LATEST_COMMIT}" \ @@ -20,19 +22,26 @@ export COMPILE_LDFLAGS='-s -w \ -X "main.BuiltOnOs=${BUILT_ON_OS}" \ -X "main.Branch=${BRANCH}" \ -X "main.CommitCnt=${COMMIT_CNT}" \ - -X "main.RuntimeVer=${RUNTIME_VER}" ' + -X "main.RuntimeVer=${RUNTIME_VER}" \ + -X "starwiz.cn/sjy01/image-proc/license/public.ECDSA_PRIVATE=${PriKeyVAL}" ' out: - GOOS=darwin GOARCH=arm64 go build -o bin/sjy01-imgproc-darwin-arm64 -ldflags=${COMPILE_LDFLAGS} cmd/*.go + GOOS=darwin GOARCH=arm64 go build -tags=local -o bin/sjy01-imgproc-darwin-arm64 -ldflags=${COMPILE_LDFLAGS} cmd/*.go clean: rm -rf ./bin/* linux: - GOOS=linux GOARCH=amd64 go build -o bin/sjy01-imgproc-v2 -ldflags=${COMPILE_LDFLAGS} cmd/*.go + GOOS=linux GOARCH=amd64 go build -tags=local -o bin/sjy01-imgproc-v2 -ldflags=${COMPILE_LDFLAGS} cmd/*.go + +linux-license: + GOOS=linux GOARCH=amd64 go build -tags=license -o bin/sjy01-imgproc-v2 -ldflags=${COMPILE_LDFLAGS} cmd/*.go release: docker run --rm -v .:/src -v /Users/lan/workspace/sjy01/build/go:/build/go nuknal/gdal38-cv49-builder sh -c "cd /src && make linux" +release-license: + docker run --rm -v .:/src -v /Users/lan/workspace/sjy01/build/go:/build/go nuknal/gdal38-cv49-builder sh -c "cd /src && make linux-license" + iau: wget -O cmd/finals2000A.all https://maia.usno.navy.mil/ser7/finals2000A.all diff --git a/cmd/main.go b/cmd/main.go index fdb2b67..2bd0f9e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,6 +2,8 @@ package main import ( _ "embed" + + "starwiz.cn/sjy01/image-proc/license" ) //go:embed finals2000A.all @@ -11,5 +13,6 @@ var eopData []byte var eopp5Line []byte func main() { + license.VerifyLicense() rootCmd.Execute() } diff --git a/go.sum b/go.sum index 55a8b12..9fbe2cc 100644 --- a/go.sum +++ b/go.sum @@ -766,6 +766,8 @@ cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +e.coding.net/zkxrsz/starwiz/starwiz-license.git v0.1.3 h1:jLW8d/W7t1i2GZshjDTRtIB3CsB8hMQZPPCRS1rR2Zo= +e.coding.net/zkxrsz/starwiz/starwiz-license.git v0.1.3/go.mod h1:6lZBmLJggC/wplylnH7Zfe4TAvwby+uzdRGVorIktKo= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= diff --git a/license/no_license.go b/license/no_license.go new file mode 100644 index 0000000..92c6642 --- /dev/null +++ b/license/no_license.go @@ -0,0 +1,10 @@ +//go:build local +// +build local + +package license + +import "fmt" + +func VerifyLicense() { + fmt.Println("license is not required") +} diff --git a/license/public/algorithm.go b/license/public/algorithm.go new file mode 100644 index 0000000..c1eb8fc --- /dev/null +++ b/license/public/algorithm.go @@ -0,0 +1,49 @@ +package public + +import ( + "crypto/ecdsa" + "encoding/pem" +) + +type NonEquAlgorthm struct { + Algorithm *SigningMethodECDSA + PrivateKey *ecdsa.PrivateKey + PublicKey *ecdsa.PublicKey +} + +func GetNonEquAlgorthm(prikey []byte, pubkey []byte) (*NonEquAlgorthm, error) { + var err error + alg := &NonEquAlgorthm{ + Algorithm: GetSignVerifyMgr(), + } + + if prikey != nil { + if block, _ := pem.Decode(prikey); block == nil { + prikey, err = FormatPem(prikey, true) + if err != nil { + return nil, err + } + } + + alg.PrivateKey, err = ParseECPrivateKeyFromPEM(prikey) + if err != nil { + return nil, err + } + } + + if pubkey != nil { + if block, _ := pem.Decode(pubkey); block == nil { + pubkey, err = FormatPem(pubkey, false) + if err != nil { + return nil, err + } + } + + alg.PublicKey, err = ParseECPublicKeyFromPEM(pubkey) + if err != nil { + return nil, err + } + } + + return alg, nil +} diff --git a/license/public/ecdsa.go b/license/public/ecdsa.go new file mode 100644 index 0000000..296cfcd --- /dev/null +++ b/license/public/ecdsa.go @@ -0,0 +1,149 @@ +package public + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "math/big" + "strings" +) + +var ( + ErrInvalidKey = errors.New("key is invalid") + ErrInvalidKeyType = errors.New("key is of invalid type") + ErrHashUnavailable = errors.New("the requested hash function is unavailable") +) + +var ( + // Sadly this is missing from crypto/ecdsa compared to crypto/rsa + ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") +) + +func GetSignVerifyMgr() *SigningMethodECDSA { + return &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} +} + +// Implements the ECDSA family of signing methods signing methods +// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification +type SigningMethodECDSA struct { + Name string + Hash crypto.Hash + KeySize int + CurveBits int +} + +func (m *SigningMethodECDSA) Alg() string { + return m.Name +} + +// Implements the Verify method from SigningMethod +// For this verify method, key must be an ecdsa.PublicKey struct +func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error { + var err error + + // Decode the signature + var sig []byte + if sig, err = DecodeSegment(signature); err != nil { + return err + } + + // Get the key + var ecdsaKey *ecdsa.PublicKey + switch k := key.(type) { + case *ecdsa.PublicKey: + ecdsaKey = k + default: + return ErrInvalidKeyType + } + + if len(sig) != 2*m.KeySize { + return ErrECDSAVerification + } + + r := big.NewInt(0).SetBytes(sig[:m.KeySize]) + s := big.NewInt(0).SetBytes(sig[m.KeySize:]) + + // Create hasher + if !m.Hash.Available() { + return ErrHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Verify the signature + if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { + return nil + } else { + return ErrECDSAVerification + } +} + +// Implements the Sign method from SigningMethod +// For this signing method, key must be an ecdsa.PrivateKey struct +func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { + // Get the key + var ecdsaKey *ecdsa.PrivateKey + switch k := key.(type) { + case *ecdsa.PrivateKey: + ecdsaKey = k + default: + return "", ErrInvalidKeyType + } + + // Create the hasher + if !m.Hash.Available() { + return "", ErrHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return r, s + if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { + curveBits := ecdsaKey.Curve.Params().BitSize + + if m.CurveBits != curveBits { + fmt.Println(m.CurveBits, curveBits) + return "", ErrInvalidKey + } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes += 1 + } + + // We serialize the outpus (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + + return EncodeSegment(out), nil + } else { + return "", err + } +} + +// Encode JWT specific base64url encoding with padding stripped +func EncodeSegment(seg []byte) string { + return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") +} + +// Decode JWT specific base64url encoding with padding stripped +func DecodeSegment(seg string) ([]byte, error) { + if l := len(seg) % 4; l > 0 { + seg += strings.Repeat("=", 4-l) + } + + return base64.URLEncoding.DecodeString(seg) +} diff --git a/license/public/errors.go b/license/public/errors.go new file mode 100644 index 0000000..eb1a713 --- /dev/null +++ b/license/public/errors.go @@ -0,0 +1,62 @@ +package public + +import "fmt" + +import "errors" + +func New(code int, msg string) *ErrorMsg { + return &ErrorMsg{code: code, msg: msg} +} + +type ErrorMsg struct { + code int + msg string + err error +} + +func (e *ErrorMsg) GetCode() int { + return e.code +} + +func (e *ErrorMsg) GetMsg() string { + return e.msg +} + +func (e *ErrorMsg) GetErr() error { + return e.err +} + +func (e *ErrorMsg) SetErr(err error) *ErrorMsg { + e.err = err + return e +} + +func (e *ErrorMsg) SetErrText(text string) *ErrorMsg { + e.err = errors.New(text) + return e +} + +func (e *ErrorMsg) Error() string { + if e.err != nil { + return fmt.Sprintf("code:%d, msg:%s, err:%s", e.code, e.msg, e.err.Error()) + } + return fmt.Sprintf("code:%d, msg:%s", e.code, e.msg) +} + +var ( + ErrNoCreateObj = New(0, "uninitialized object") + ErrUnKnown = New(-1, "unknown error") + ErrDirNoExist = New(-2, "dir does not exist") + ErrNewWatcher = New(-3, "new watcher object failed") + ErrWatcherAdd = New(-4, "watcher add dir failed") + ErrLoadPubKey = New(-5, "failed to load public key") + ErrReadAuthFile = New(-6, "reading authorization file failed") + ErrDecodeAuthFile = New(-7, "decode authorization file failed") + ErrVerifySign = New(-8, "failed to verify signature") + ErrUnmarshalLiObj = New(-9, "unmarshal license object failed") + ErrGetMachineCode = New(-10, "failed to get machine code") + ErrNoMatchProName = New(-11, "product name does not match") + ErrLicenseExpired = New(-12, "license is expired") + ErrBeforeIssued = New(-13, "license used before issued") + ErrNoMatchMachineID = New(-14, "machine id does not match") +) diff --git a/license/public/file.go b/license/public/file.go new file mode 100644 index 0000000..b1b483f --- /dev/null +++ b/license/public/file.go @@ -0,0 +1,120 @@ +package public + +import ( + "bufio" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +func Exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +func SaveLicensePem(dir string, filename string, licenseString string, headers map[string]string) error { + isExist := Exists(dir) + if isExist == false && dir != "." { + err := os.MkdirAll(dir, os.ModePerm) + if err != nil { + return err + } + } + + savePath := filepath.Join(dir, filename) + + fd, err := os.OpenFile(savePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer fd.Close() + + w := bufio.NewWriter(fd) + block := &pem.Block{ + Type: "LICENSE", + Headers: headers, + Bytes: []byte(licenseString), + } + + if err := pem.Encode(w, block); err != nil { + return err + } + + if err := w.Flush(); err != nil { + return err + } + return nil +} + +func CheckPemAndSave(filePath string, licenseBytes []byte) (*pem.Block, error) { + var ( + block *pem.Block + ) + + saveDir := filepath.Dir(filePath) + fmt.Println(saveDir) + + isExist := Exists(saveDir) + if isExist == false { + err := os.MkdirAll(saveDir, os.ModePerm) + if err != nil { + return nil, err + } + } + + if block, _ = pem.Decode(licenseBytes); block == nil { + return nil, fmt.Errorf("%s", "license must be PEM") + } + + fd, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return nil, err + } + defer fd.Close() + + _, err = fd.Write(licenseBytes) + if err != nil { + return nil, err + } + + return block, nil +} + +func ReadLicensePem(filePath string) ([]byte, error) { + isExist := Exists(filePath) + if isExist == false { + return nil, fmt.Errorf("%s", "license file does not exist") + } + + fd, err := os.OpenFile(filePath, os.O_RDONLY, 0644) + if err != nil { + return nil, err + } + defer fd.Close() + + licenseBytes, err := ioutil.ReadAll(fd) + if err != nil { + return nil, err + } + + // block, _ := pem.Decode(licenseBytes) + + return licenseBytes, nil +} + +func LoadKey(pemFile string) ([]byte, error) { + context, err := ioutil.ReadFile(pemFile) + if err != nil { + return nil, fmt.Errorf("failed to read key file: %s, error: %s", pemFile, err) + } + + return context, nil +} diff --git a/license/public/key.go b/license/public/key.go new file mode 100644 index 0000000..7be7277 --- /dev/null +++ b/license/public/key.go @@ -0,0 +1,68 @@ +package public + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +var ( + ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") + ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") + ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") +) + +// Parse PEM encoded Elliptic Curve Private Key Structure +func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { + return nil, err + } + + var pkey *ecdsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { + return nil, ErrNotECPrivateKey + } + + return pkey, nil +} + +// Parse PEM encoded PKCS1 or PKCS8 public key +func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *ecdsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { + return nil, ErrNotECPublicKey + } + + return pkey, nil +} diff --git a/license/public/license.go b/license/public/license.go new file mode 100644 index 0000000..3a9e0ff --- /dev/null +++ b/license/public/license.go @@ -0,0 +1,167 @@ +package public + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "time" +) + +type License struct { + LicenseUUID string `json:"licensever,omitempty"` //license 唯一编号 + ProductName string `json:"productname,omitempty"` //产品名称 + MachineID string `json:"machineid,omitempty"` //机器ID + ExpiresAt int64 `json:"expiresat,omitempty"` //过期时间 + IssuedAt int64 `json:"issuedat,omitempty"` //签发时间 + CustomKV map[string]string `json:"customkv,omitempty"` +} + +func GenerateLicense(uuid string, productName string, machineID string, expires int64, kv map[string]string) *License { + return &License{ + LicenseUUID: uuid, + ProductName: productName, + MachineID: machineID, + ExpiresAt: expires, + IssuedAt: time.Now().Unix(), + CustomKV: kv, + } +} + +func VerifyLicense(productName string, machine string, licenseBytes []byte, isVerify bool) (*License, error) { + l := new(License) + if err := json.Unmarshal(licenseBytes, l); err != nil { + return nil, err + } + + if isVerify { + err := l.Valid(productName, machine) + if err != nil { + return nil, err + } + } + + return l, nil +} + +func (c *License) Valid(productName string, machine string) error { + var vErr error + now := time.Now().Unix() + + //比较产品名称 + if c.CompareProductName(productName) == false { + vErr = ErrNoMatchProName + } + + //比较过期时间 + if c.VerifyExpiresAt(now, false) == false { + // delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) + expStr := time.Unix(c.ExpiresAt, 0).Format("2006-01-02 15:04:05") + vErr = ErrLicenseExpired.SetErrText(fmt.Sprintf("license is expired after %s", expStr)) + } + + //比较签发时间 + if c.VerifyIssuedAt(now, false) == false { + vErr = ErrBeforeIssued + } + + //比较机器是否与license匹配 + if c.CompareMachine(machine) == false { + vErr = ErrNoMatchMachineID + } + return vErr +} + +//已经过期返回0,未过期返回剩余的秒数 +func (c *License) GetExpiresAt() int64 { + now := time.Now().Unix() + delta := time.Unix(c.ExpiresAt, 0).Sub(time.Unix(now, 0)) + if delta <= 0 { // + return 0 + } + return int64(delta.Seconds()) +} + +func (c *License) GetEndTime() string { + return time.Unix(c.ExpiresAt, 0).String() +} + +//比较产品名 +func (c *License) CompareProductName(productName string) bool { + return strings.Compare(c.ProductName, productName) == 0 +} + +//比较过期时间 +func (c *License) VerifyExpiresAt(now int64, req bool) bool { + if c.ExpiresAt == 0 { + return !req + } + return now <= c.ExpiresAt +} + +//比较签发时间 +func (c *License) VerifyIssuedAt(now int64, req bool) bool { + if c.IssuedAt == 0 { + return !req + } + return now >= c.IssuedAt +} + +//比较机器ID +func (c *License) CompareMachine(machineID string) bool { + return strings.Compare(c.MachineID, machineID) == 0 +} + +//编码 +func (c *License) ToBytes() ([]byte, error) { + var ( + jsonValue []byte + err error + ) + if jsonValue, err = json.Marshal(c); err != nil { + return nil, err + } + + return jsonValue, nil +} + +func ToLicense(lb []byte) (*License, error) { + l := new(License) + err := json.Unmarshal(lb, l) + if err != nil { + return nil, err + } + + return l, nil +} + +func BytesToLicense(license string) (*License, error) { + parts := strings.Split(license, ".") + if len(parts) != 2 { + return nil, fmt.Errorf("%s", "license contains an invalid number of segments") + } + + plainBytes, err := DecodeSegment(parts[0]) + if err != nil { + return nil, err + } + + l := new(License) + err = json.Unmarshal(plainBytes, l) + if err != nil { + return nil, err + } + + return l, nil +} + +func Struct2Map(obj interface{}) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + + var data = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + data[t.Field(i).Name] = v.Field(i).Interface() + } + return data +} diff --git a/license/public/machine.go b/license/public/machine.go new file mode 100644 index 0000000..0359b5b --- /dev/null +++ b/license/public/machine.go @@ -0,0 +1,203 @@ +package public + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strconv" + "strings" +) + +const ( + DMIDECODE = "dmidecode" +) + +func CheckCmdExists(command string) (string, error) { + path, err := exec.LookPath(command) + if err != nil { + if runtime.GOOS != "darwin" || //macos,windows测试使用,不获取硬盘分区UUID + runtime.GOOS != "windows" { + fmt.Printf("didn't find 'blkid' executable,err %s\n", err.Error()) + } + return "", err + } + return path, nil +} + +func IsExistFile(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +func ReadSysFile(filePath string) ([]byte, error) { + isExist := IsExistFile(filePath) + if isExist == false { + return nil, fmt.Errorf("%s file does not exist", filePath) + } + + fd, err := os.OpenFile(filePath, os.O_RDONLY, 0644) + if err != nil { + if os.IsPermission(err) { + return nil, fmt.Errorf("%s", "permission denied get machine id") + } + return nil, fmt.Errorf("%s", "get machine id failed") + } + defer fd.Close() + + fstabText, err := ioutil.ReadAll(fd) + if err != nil { + return nil, err + } + + return fstabText, nil +} + +//降低数字串长度 +func Sum(data []byte) string { + var ( + sum uint64 + length int = len(data) + index int + ) + + //以32位求和 + for length >= 4 { + sum += uint64(data[index])<<24 + uint64(data[index+1])<<16 + uint64(data[index+2])<<8 + uint64(data[index+3]) + index += 4 + length -= 4 + } + + switch length { + case 3: + sum += uint64(data[index])<<16 + uint64(data[index+1])<<8 + uint64(data[index+2]) + case 2: + sum += uint64(data[index])<<8 + uint64(data[index+1]) + case 1: + sum += uint64(data[index]) + case 0: + break + } + + return strconv.FormatUint(sum, 16) +} + +//获取BIOS出厂UUID +func GetProductUUID() (string, error) { + if runtime.GOOS == "darwin" { + return GetProductUUIDMac() + } + + if runtime.GOOS == "windows" { + return GetProductUUIDWin() + } + + cmd := exec.Command("dmidecode", "-s", "system-uuid") + output, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + + if len(output) == 0 { + return "", fmt.Errorf("%s", "uuid is empty string") + } + + system_uuid := string(output) + + cmd = exec.Command("dmidecode", "-s", "system-serial-number") + output, err = cmd.CombinedOutput() + if err != nil { + return "", err + } + + if len(output) == 0 { + return "", fmt.Errorf("%s", "serial-number is empty string") + } + + system_serial_number := string(output) + + deviceID := system_uuid + "_starwiz_" + system_serial_number + return GetMd5String(deviceID), nil +} + +/* +GetMachineID 获取机器ID (分区UUID和网卡地址 or Product UUID) +*/ +func GetMachineID() (string, error) { + var ( + MachineIDList []string + ) + + productUUID, err := GetProductUUID() + if err != nil { + return "", err + } + MachineIDList = append(MachineIDList, productUUID) + + //编码 + encByte, err := json.Marshal(MachineIDList) + if err != nil { + return "", err + } + + //降低长度 + return Sum(encByte), nil +} + +func GetMd5String(s string) string { + h := md5.New() + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) +} + +func GetProductUUIDMac() (string, error) { + // 从系统调用获取硬件 UUID + out, err := exec.Command("system_profiler", "SPHardwareDataType").Output() + if err != nil { + return "", err + } + + // 解析输出以获取 UUID + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if strings.Contains(line, "Hardware UUID:") { + parts := strings.Split(line, ":") + if len(parts) == 2 { + return strings.TrimSpace(parts[1]), nil + } + } + } + + return "", fmt.Errorf("could not find hardware UUID in system_profiler output") +} + +func GetProductUUIDWin() (string, error) { + // 使用系统调用获取计算机的注册表信息 + out, err := exec.Command("reg", "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid").Output() + if err != nil { + return "", err + } + + // 解析注册表输出以获取 MachineGuid + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if strings.Contains(line, "REG_SZ") { + parts := strings.Fields(line) + if len(parts) >= 3 { + return strings.TrimSpace(parts[2]), nil + } + } + } + + return "", fmt.Errorf("MachineGuid not found in registry") +} diff --git a/license/public/privatekey.go b/license/public/privatekey.go new file mode 100644 index 0000000..a0f474b --- /dev/null +++ b/license/public/privatekey.go @@ -0,0 +1,29 @@ +package public + +import "strings" + +var ( + ECDSA_PRIVATE = "" + +// ECDSA_PRIVATE = `-----BEGIN EC PRIVATE KEY----- +// MIHcAgEBBEIB0pE4uFaWRx7t03BsYlYvF1YvKaBGyvoakxnodm9ou0R9wC+sJAjH +// QZZJikOg4SwNqgQ/hyrOuDK2oAVHhgVGcYmgBwYFK4EEACOhgYkDgYYABAAJXIuw +// 12MUzpHggia9POBFYXSxaOGKGbMjIyDI+6q7wi7LMw3HgbaOmgIqFG72o8JBQwYN +// 4IbXHf+f86CRY1AA2wHzbHvt6IhkCXTNxBEffa1yMUgu8n9cKKF2iLgyQKcKqW33 +// 8fGOw/n3Rm2Yd/EB56u2rnD29qS+nOM9eGS+gy39OQ== +// -----END EC PRIVATE KEY-----` +) + +func (nea *NonEquAlgorthm) SignedBytes(plainText []byte) (string, error) { + var ( + sig string + err error + ) + + encBase64String := EncodeSegment(plainText) + if sig, err = nea.Algorithm.Sign(encBase64String, nea.PrivateKey); err != nil { + return "", err + } + + return strings.Join([]string{encBase64String, sig}, "."), nil +} diff --git a/license/public/public.go b/license/public/public.go new file mode 100644 index 0000000..57eaf0c --- /dev/null +++ b/license/public/public.go @@ -0,0 +1,100 @@ +package public + +import ( + "bytes" + "fmt" + "os" + + "github.com/google/uuid" +) + +func GetUUID() string { + return uuid.New().String() +} + +func CheckFileIsExist(filename string) bool { + var exist = true + if _, err := os.Stat(filename); os.IsNotExist(err) { + exist = false + } + return exist +} + +func RemoveDuplicate(list []int) []int { + var x []int + for _, i := range list { + if len(x) == 0 { + x = append(x, i) + } else { + for k, v := range x { + if i == v { + break + } + if k == len(x)-1 { + x = append(x, i) + } + } + } + } + return x +} + +func FormatPem(content []byte, isPriKey bool) ([]byte, error) { + var ( + pemPubKeyStart = []byte("-----BEGIN PUBLIC KEY-----") + pemPubKeyEnd = []byte("-----END PUBLIC KEY-----") + + pemPriKeyStart = []byte("-----BEGIN EC PRIVATE KEY-----") + pemPriKeyEnd = []byte("-----END EC PRIVATE KEY-----") + + orgContent []byte + pemContent []byte + slen = len(content) + ) + + if slen == 0 { + if isPriKey && slen < (len(pemPriKeyStart)+len(pemPriKeyEnd)) { + return nil, fmt.Errorf("%s", "prikey centent length not enough") + } else if slen < (len(pemPubKeyStart) + len(pemPubKeyEnd)) { + return nil, fmt.Errorf("%s", "pubkey centent length not enough") + } + return nil, fmt.Errorf("%s", "content not null") + } else { + orgContent = make([]byte, slen) + copy(orgContent, content) + orgContent = bytes.Join(bytes.Fields(orgContent), []byte(" ")) + slen = len(orgContent) + } + + if isPriKey { + //私钥 + if bytes.HasPrefix(orgContent, pemPriKeyStart) && bytes.HasSuffix(orgContent, pemPriKeyEnd) { + pemContent = append(pemContent, pemPriKeyStart...) + for _, v := range orgContent[len(pemPriKeyStart) : slen-len(pemPriKeyEnd)] { + if v == ' ' { + pemContent = append(pemContent, []byte("\n")...) + } else { + pemContent = append(pemContent, v) + } + } + pemContent = append(pemContent, pemPriKeyEnd...) + } else { + return nil, fmt.Errorf("%s", "pem file prefix must be 'BEGIN/END EC PRIVATE KEY' format") + } + } else { + if bytes.HasPrefix(orgContent, pemPubKeyStart) && bytes.HasSuffix(orgContent, pemPubKeyEnd) { + pemContent = append(pemContent, pemPubKeyStart...) + for _, v := range orgContent[len(pemPubKeyStart) : slen-len(pemPubKeyEnd)] { + if v == ' ' { + pemContent = append(pemContent, []byte("\n")...) + } else { + pemContent = append(pemContent, v) + } + } + pemContent = append(pemContent, pemPubKeyEnd...) + } else { + return nil, fmt.Errorf("%s", "pem file prefix must be 'BEGIN/END PUBLIC KEY' format") + } + } + return pemContent, nil +} diff --git a/license/public/publickey.go b/license/public/publickey.go new file mode 100644 index 0000000..44a08bb --- /dev/null +++ b/license/public/publickey.go @@ -0,0 +1,39 @@ +package public + +import ( + "fmt" + "strings" +) + +var ( + ECDSA_PUBLICKEY = "" + +// ECDSA_PUBLICKEY = `-----BEGIN PUBLIC KEY----- +// MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQACVyLsNdjFM6R4IImvTzgRWF0sWjh +// ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7 +// 7eiIZAl0zcQRH32tcjFILvJ/XCihdoi4MkCnCqlt9/HxjsP590ZtmHfxAeertq5w +// 9vakvpzjPXhkvoMt/Tk= +// -----END PUBLIC KEY-----` +) + +func (nea *NonEquAlgorthm) VerifySign(cipherText string) ([]byte, error) { + var ( + err error + ) + + parts := strings.Split(cipherText, ".") + if len(parts) != 2 { + return nil, fmt.Errorf("%s", "license contains an invalid number of segments") + } + + if err = nea.Algorithm.Verify(parts[0], parts[1], nea.PublicKey); err != nil { + return nil, err + } + + plainBytes, err := DecodeSegment(parts[0]) + if err != nil { + return nil, err + } + + return plainBytes, nil +} diff --git a/license/public/version.go b/license/public/version.go new file mode 100644 index 0000000..ce4c2bf --- /dev/null +++ b/license/public/version.go @@ -0,0 +1,24 @@ +package public + +import ( + "bytes" +) + +var ( + buildName = "switch-license" + buildVersion = "development" + buildBranch = "development" + buildCommitID = "development" + buildTime = "development" +) + +func GetAppInfo() string { + b := bytes.NewBufferString("\nAppInfo:") + b.WriteString("\n Name: " + buildName) + b.WriteString("\n Version: " + buildVersion) + b.WriteString("\n Branch: " + buildBranch) + b.WriteString("\n CommitID: " + buildCommitID) + b.WriteString("\n BuildTime: " + buildTime) + b.WriteString("\n") + return b.String() +} diff --git a/license/with_license.go b/license/with_license.go new file mode 100644 index 0000000..51d5487 --- /dev/null +++ b/license/with_license.go @@ -0,0 +1,53 @@ +//go:build license +// +build license + +package license + +import ( + "encoding/pem" + "fmt" + "log" + "os" + "runtime" + + "starwiz.cn/sjy01/image-proc/license/public" +) + +var ProductName = "sjy01-data-processing" + +func VerifyLicense() { + machineID, err := public.GetMachineID() + if err != nil { + fmt.Println(err) + } + fmt.Println("machine ID:", machineID) + fmt.Println("product name:", ProductName) + + if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { + fmt.Println("license is not required on this platform:", runtime.GOOS) + return + } + + value, err := public.LoadKey("license.lic") + if err != nil { + log.Println("invalid license", err) + os.Exit(130) + } + + var block *pem.Block + if block, _ = pem.Decode(value); block == nil { + fmt.Printf("%s\n", "license must be PEM") + os.Exit(130) + } + + l, err := public.BytesToLicense(string(block.Bytes)) + if err != nil { + fmt.Println("invalid license", err) + os.Exit(131) + } + + if err := l.Valid(ProductName, machineID); err != nil { + fmt.Println("invalid license", err) + os.Exit(132) + } +}