增加授权验证
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ build/go
|
||||
*.tif
|
||||
*.tiff
|
||||
dem/*
|
||||
license/keys
|
||||
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"gopls": {
|
||||
"build.buildFlags": ["-tags=local"]
|
||||
}
|
||||
}
|
||||
15
Makefile
15
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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
10
license/no_license.go
Normal file
10
license/no_license.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build local
|
||||
// +build local
|
||||
|
||||
package license
|
||||
|
||||
import "fmt"
|
||||
|
||||
func VerifyLicense() {
|
||||
fmt.Println("license is not required")
|
||||
}
|
||||
49
license/public/algorithm.go
Normal file
49
license/public/algorithm.go
Normal file
@@ -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
|
||||
}
|
||||
149
license/public/ecdsa.go
Normal file
149
license/public/ecdsa.go
Normal file
@@ -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)
|
||||
}
|
||||
62
license/public/errors.go
Normal file
62
license/public/errors.go
Normal file
@@ -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")
|
||||
)
|
||||
120
license/public/file.go
Normal file
120
license/public/file.go
Normal file
@@ -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
|
||||
}
|
||||
68
license/public/key.go
Normal file
68
license/public/key.go
Normal file
@@ -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
|
||||
}
|
||||
167
license/public/license.go
Normal file
167
license/public/license.go
Normal file
@@ -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
|
||||
}
|
||||
203
license/public/machine.go
Normal file
203
license/public/machine.go
Normal file
@@ -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")
|
||||
}
|
||||
29
license/public/privatekey.go
Normal file
29
license/public/privatekey.go
Normal file
@@ -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
|
||||
}
|
||||
100
license/public/public.go
Normal file
100
license/public/public.go
Normal file
@@ -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
|
||||
}
|
||||
39
license/public/publickey.go
Normal file
39
license/public/publickey.go
Normal file
@@ -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
|
||||
}
|
||||
24
license/public/version.go
Normal file
24
license/public/version.go
Normal file
@@ -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()
|
||||
}
|
||||
53
license/with_license.go
Normal file
53
license/with_license.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user