Files
license/sdk/licence/binary.go

203 lines
5.7 KiB
Go
Raw Normal View History

2026-07-31 16:59:55 +08:00
package licence
import (
"crypto/ed25519"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"math"
"unicode/utf8"
)
const (
formatVersion = 1
certificateDomain = "OPS-LICENCE-ISSUER-CERT/V1"
claimsDomain = "OPS-LICENCE/V1"
maximumLicenceFileBytes = 64 * 1024
)
var base64URL = base64.RawURLEncoding.Strict()
func CertificateMessage(c SigningKeyCertificate) ([]byte, error) {
if c.FormatVersion != formatVersion {
return nil, ErrUnsupportedVersion
}
keyID, err := parseUUID(c.KeyID)
if err != nil {
return nil, fmt.Errorf("%w: 凭证密钥 ID 格式错误", ErrInvalidKeyCertificate)
}
publicKey, err := decodeBase64URL(c.PublicKey, ed25519.PublicKeySize)
if err != nil {
return nil, fmt.Errorf("%w: 凭证公钥格式错误", ErrInvalidKeyCertificate)
}
message := make([]byte, 0, len(certificateDomain)+len(keyID)+len(publicKey))
message = append(message, certificateDomain...)
message = append(message, keyID[:]...)
message = append(message, publicKey...)
return message, nil
}
func ClaimsMessage(c Claims) ([]byte, error) {
licenceID, err := parseUUID(c.ID)
if err != nil {
return nil, fmt.Errorf("%w: 许可证 ID 格式错误", ErrMalformedFile)
}
signingKeyID, err := parseUUID(c.SigningKeyID)
if err != nil {
return nil, fmt.Errorf("%w: 签发密钥 ID 格式错误", ErrMalformedFile)
}
if err := validateQuotas(c.Quotas); err != nil {
return nil, err
}
issuedOn, err := encodedDate(c.IssuedOn)
if err != nil {
return nil, fmt.Errorf("%w: 签发日期格式错误", ErrMalformedFile)
}
validFrom, err := encodedDate(c.ValidFrom)
if err != nil {
return nil, fmt.Errorf("%w: 生效日期格式错误", ErrMalformedFile)
}
expiresOn, err := encodedDate(c.ExpiresOn)
if err != nil {
return nil, fmt.Errorf("%w: 到期日期格式错误", ErrMalformedFile)
}
message := make([]byte, 0, 256)
message = append(message, claimsDomain...)
message = append(message, licenceID[:]...)
message, err = appendLengthPrefixedString(message, c.PlatformName)
if err != nil {
return nil, fmt.Errorf("%w: 平台名称编码错误", ErrMalformedFile)
}
message, err = appendLengthPrefixedString(message, c.Workspace)
if err != nil {
return nil, fmt.Errorf("%w: workspace 编码错误", ErrMalformedFile)
}
message = append(message, issuedOn...)
message = append(message, validFrom...)
message = append(message, expiresOn...)
for _, quota := range quotaValues(c.Quotas) {
message = binary.BigEndian.AppendUint64(message, uint64(quota.value))
}
message = append(message, signingKeyID[:]...)
return message, nil
}
func appendLengthPrefixedString(destination []byte, value string) ([]byte, error) {
if !utf8.ValidString(value) {
return nil, errors.New("字符串不是有效的 UTF-8")
}
if uint64(len(value)) > math.MaxUint32 {
return nil, errors.New("字符串长度超过 uint32")
}
destination = binary.BigEndian.AppendUint32(destination, uint32(len(value)))
destination = append(destination, value...)
return destination, nil
}
func encodedDate(date Date) ([]byte, error) {
if _, err := date.Time(); err != nil {
return nil, err
}
value := date.String()
if len(value) != len(dateLayout) {
return nil, errors.New("日期长度错误")
}
return []byte(value), nil
}
type quotaValue struct {
name string
value int64
}
func quotaValues(quotas Quotas) [11]quotaValue {
return [11]quotaValue{
{name: "max_database", value: quotas.MaxDatabase},
{name: "max_middleware", value: quotas.MaxMiddleware},
{name: "max_network_device", value: quotas.MaxNetworkDevice},
{name: "max_security", value: quotas.MaxSecurity},
{name: "max_storage", value: quotas.MaxStorage},
{name: "max_pc", value: quotas.MaxPC},
{name: "max_server", value: quotas.MaxServer},
{name: "max_user", value: quotas.MaxUser},
{name: "max_role", value: quotas.MaxRole},
{name: "max_permission", value: quotas.MaxPermission},
{name: "max_menu", value: quotas.MaxMenu},
}
}
func validateQuotas(quotas Quotas) error {
for _, quota := range quotaValues(quotas) {
if quota.value < 0 {
return fmt.Errorf("%w: %s 不能为负数", ErrInvalidQuota, quota.name)
}
}
return nil
}
func decodeBase64URL(value string, expectedLength int) ([]byte, error) {
for index := range value {
character := value[index]
if (character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z') ||
(character >= '0' && character <= '9') ||
character == '-' || character == '_' {
continue
}
return nil, errors.New("Base64URL 包含非法字符")
}
decoded, err := base64URL.DecodeString(value)
if err != nil {
return nil, errors.New("Base64URL 编码错误")
}
if len(decoded) != expectedLength {
return nil, errors.New("解码后长度错误")
}
return decoded, nil
}
func parseUUID(value string) ([16]byte, error) {
var result [16]byte
if len(value) != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' {
return result, errors.New("UUID 必须使用 8-4-4-4-12 格式")
}
byteIndex := 0
for index := 0; index < len(value); {
if index == 8 || index == 13 || index == 18 || index == 23 {
index++
continue
}
high, ok := hexadecimalValue(value[index])
if !ok {
return result, errors.New("UUID 包含非十六进制字符")
}
low, ok := hexadecimalValue(value[index+1])
if !ok {
return result, errors.New("UUID 包含非十六进制字符")
}
result[byteIndex] = high<<4 | low
byteIndex++
index += 2
}
return result, nil
}
func hexadecimalValue(value byte) (byte, bool) {
switch {
case value >= '0' && value <= '9':
return value - '0', true
case value >= 'a' && value <= 'f':
return value - 'a' + 10, true
case value >= 'A' && value <= 'F':
return value - 'A' + 10, true
default:
return 0, false
}
}