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 } }