mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-29 13:48:54 +08:00
License package added to v2.
This commit is contained in:
parent
10c3be8c95
commit
1a5c918307
164
license/crypto.go
Normal file
164
license/crypto.go
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package license
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/unidoc/unidoc/common"
|
||||
)
|
||||
|
||||
const (
|
||||
licenseKeyHeader = "-----BEGIN UNIDOC LICENSE KEY-----"
|
||||
licenseKeyFooter = "-----END UNIDOC LICENSE KEY-----"
|
||||
)
|
||||
|
||||
// Returns signed content in a base64 format which is in format:
|
||||
//
|
||||
// Base64OriginalContent
|
||||
// +
|
||||
// Base64Signature
|
||||
func signContent(privKey string, content []byte) (string, error) {
|
||||
privBlock, _ := pem.Decode([]byte(privKey))
|
||||
if privBlock == nil {
|
||||
return "", fmt.Errorf("PrivKey failed")
|
||||
}
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(privBlock.Bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash := sha512.New()
|
||||
hash.Write(content)
|
||||
digest := hash.Sum(nil)
|
||||
|
||||
signature, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA512, digest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ret := base64.StdEncoding.EncodeToString(content)
|
||||
ret += "\n+\n"
|
||||
ret += base64.StdEncoding.EncodeToString(signature)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Verifies and reconstructs the original content
|
||||
func verifyContent(pubKey string, content string) ([]byte, error) {
|
||||
// Empty + line is the delimiter between content and signature.
|
||||
// We need to cope with both unix and windows newline, default to unix
|
||||
// one and try Windows one as fallback.
|
||||
separator := "\n+\n"
|
||||
separatorFallback := "\r\n+\r\n"
|
||||
|
||||
sepIdx := strings.Index(content, separator)
|
||||
if sepIdx == -1 {
|
||||
sepIdx = strings.Index(content, separatorFallback)
|
||||
if sepIdx == -1 {
|
||||
return nil, fmt.Errorf("Invalid input, signature separator")
|
||||
}
|
||||
}
|
||||
|
||||
// Original is from start until the separator - 1
|
||||
original := content[:sepIdx]
|
||||
|
||||
// Signature is from after the separator until the end of file.
|
||||
signatureStarts := sepIdx + len(separator)
|
||||
signature := content[signatureStarts:]
|
||||
|
||||
if original == "" || signature == "" {
|
||||
return nil, fmt.Errorf("Invalid input, missing original or signature")
|
||||
}
|
||||
|
||||
originalBytes, err := base64.StdEncoding.DecodeString(original)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid input original")
|
||||
}
|
||||
|
||||
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid input signature")
|
||||
}
|
||||
|
||||
pubBlock, _ := pem.Decode([]byte(pubKey))
|
||||
if pubBlock == nil {
|
||||
return nil, fmt.Errorf("PubKey failed")
|
||||
}
|
||||
|
||||
tempPub, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pub := tempPub.(*rsa.PublicKey)
|
||||
if pub == nil {
|
||||
return nil, fmt.Errorf("PubKey conversion failed")
|
||||
}
|
||||
|
||||
hash := sha512.New()
|
||||
hash.Write(originalBytes)
|
||||
digest := hash.Sum(nil)
|
||||
|
||||
err = rsa.VerifyPKCS1v15(pub, crypto.SHA512, digest, signatureBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return originalBytes, nil
|
||||
}
|
||||
|
||||
// Returns the content wrap around the headers
|
||||
func getWrappedContent(header string, footer string, content string) (string, error) {
|
||||
// Find all content between header and footer.
|
||||
headerIdx := strings.Index(content, header)
|
||||
if headerIdx == -1 {
|
||||
return "", fmt.Errorf("Header not found")
|
||||
}
|
||||
|
||||
footerIdx := strings.Index(content, footer)
|
||||
if footerIdx == -1 {
|
||||
return "", fmt.Errorf("Footer not found")
|
||||
}
|
||||
|
||||
start := headerIdx + len(header) + 1
|
||||
return content[start : footerIdx-1], nil
|
||||
}
|
||||
|
||||
func licenseKeyDecode(content string) (LicenseKey, error) {
|
||||
var ret LicenseKey
|
||||
|
||||
data, err := getWrappedContent(licenseKeyHeader, licenseKeyFooter, content)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
verifiedRet, err := verifyContent(pubKey, data)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(verifiedRet, &ret)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
ret.CreatedAt = time.Unix(ret.CreatedAtInt, 0)
|
||||
ret.ExpiresAt = time.Unix(ret.ExpiresAtInt, 0)
|
||||
|
||||
return ret, nil
|
||||
}
|
137
license/key.go
Normal file
137
license/key.go
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package license
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
)
|
||||
|
||||
const (
|
||||
LicenseTypeCommercial = "commercial"
|
||||
LicenseTypeOpensource = "opensource"
|
||||
)
|
||||
|
||||
const opensourceLicenseId = "01aa523c-b4c6-4d57-bbdd-5a88d2bd5300"
|
||||
const opensourceLicenseUuid = "01aa523c-b4c6-4d57-bbdd-5a88d2bd5301"
|
||||
|
||||
func getSupportedFeatures() []string {
|
||||
return []string{"unidoc", "unidoc-cli"}
|
||||
}
|
||||
|
||||
// Make sure all time is at least after this for sanity check.
|
||||
var testTime = time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
type LicenseKey struct {
|
||||
LicenseId string `json:"license_id"`
|
||||
CustomerId string `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
Type string `json:"type"`
|
||||
Features []string `json:"features"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
CreatedAtInt int64 `json:"created_at"`
|
||||
ExpiresAt time.Time `json:"-"`
|
||||
ExpiresAtInt int64 `json:"expires_at"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
CreatorName string `json:"creator_name"`
|
||||
CreatorEmail string `json:"creator_email"`
|
||||
}
|
||||
|
||||
func (this *LicenseKey) Validate() error {
|
||||
if len(this.LicenseId) < 10 {
|
||||
return fmt.Errorf("Invalid license: License Id")
|
||||
}
|
||||
|
||||
if len(this.CustomerId) < 10 {
|
||||
return fmt.Errorf("Invalid license: Customer Id")
|
||||
}
|
||||
|
||||
if len(this.CustomerName) < 1 {
|
||||
return fmt.Errorf("Invalid license: Customer Name")
|
||||
}
|
||||
|
||||
if this.Features == nil || len(this.Features) < 1 {
|
||||
return fmt.Errorf("Invalid license: No features")
|
||||
}
|
||||
|
||||
for _, feature := range this.Features {
|
||||
found := false
|
||||
|
||||
for _, sf := range getSupportedFeatures() {
|
||||
if sf == feature {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Invalid license: Unsupported feature %s", feature)
|
||||
}
|
||||
}
|
||||
|
||||
if testTime.After(this.CreatedAt) {
|
||||
return fmt.Errorf("Invalid license: Created At is invalid")
|
||||
}
|
||||
|
||||
if this.CreatedAt.After(this.ExpiresAt) {
|
||||
return fmt.Errorf("Invalid license: Created At cannot be Greater than Expires At")
|
||||
}
|
||||
|
||||
if common.ReleasedAt.After(this.ExpiresAt) {
|
||||
return fmt.Errorf("Expired license, expired at: %s", common.UtcTimeFormat(this.ExpiresAt))
|
||||
}
|
||||
|
||||
if len(this.CreatorName) < 1 {
|
||||
return fmt.Errorf("Invalid license: Creator name")
|
||||
}
|
||||
|
||||
if len(this.CreatorEmail) < 1 {
|
||||
return fmt.Errorf("Invalid license: Creator email")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *LicenseKey) TypeToString() string {
|
||||
ret := "AGPLv3 Open Source License"
|
||||
|
||||
if this.Type == LicenseTypeCommercial {
|
||||
ret = "Commercial License"
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (this *LicenseKey) ToString() string {
|
||||
str := fmt.Sprintf("License Id: %s\n", this.LicenseId)
|
||||
str += fmt.Sprintf("Customer Id: %s\n", this.CustomerId)
|
||||
str += fmt.Sprintf("Customer Name: %s\n", this.CustomerName)
|
||||
str += fmt.Sprintf("Type: %s\n", this.Type)
|
||||
str += fmt.Sprintf("Features: %s\n", strings.Join(this.Features, ", "))
|
||||
str += fmt.Sprintf("Created At: %s\n", common.UtcTimeFormat(this.CreatedAt))
|
||||
str += fmt.Sprintf("Expires At: %s\n", common.UtcTimeFormat(this.ExpiresAt))
|
||||
str += fmt.Sprintf("Creator: %s <%s>\n", this.CreatorName, this.CreatorEmail)
|
||||
return str
|
||||
}
|
||||
|
||||
func MakeOpensourceLicenseKey() *LicenseKey {
|
||||
lk := LicenseKey{}
|
||||
lk.LicenseId = opensourceLicenseId
|
||||
lk.CustomerId = opensourceLicenseUuid
|
||||
lk.CustomerName = "Open Source Evangelist"
|
||||
lk.Type = LicenseTypeOpensource
|
||||
lk.Features = getSupportedFeatures()
|
||||
lk.CreatedAt = time.Now().UTC()
|
||||
lk.CreatedAtInt = lk.CreatedAt.Unix()
|
||||
lk.ExpiresAt = lk.CreatedAt.AddDate(10, 0, 0)
|
||||
lk.ExpiresAtInt = lk.ExpiresAt.Unix()
|
||||
lk.CreatorName = "UniDoc Support"
|
||||
lk.CreatorEmail = "support@unidoc.io"
|
||||
return &lk
|
||||
}
|
19
license/pubkeys.go
Normal file
19
license/pubkeys.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package license
|
||||
|
||||
// Public key
|
||||
const pubKey = `
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFUiyd7b5XjpkP5Rap4w
|
||||
Dc1dyzIQ4LekxrvytnEMpNUbo6iA74V8ruZOvrScsf2QeN9/qrUG8qEbUWdoEYq+
|
||||
otFNAFNxlGbxbDHcdGVaM0OXdXgDyL5aIEagL0c5pwjIdPGIn46f78eMJ+JkdcpD
|
||||
DJaqYXdrz5KeshjSiIaa7menBIAXS4UFxNfHhN0HCYZYqQG7bK+s5rRHonydNWEG
|
||||
H8Myvr2pya2KrMumfmAxUB6fenC/4O0Wr8gfPOU8RitmbDvQPIRXOL4vTBrBdbaA
|
||||
9nwNP+i//20MT2bxmeWB+gpcEhGpXZ733azQxrC3J4v3CZmENStDK/KDSPKUGfu6
|
||||
fwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
33
license/util.go
Normal file
33
license/util.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
// Package license contains customer license handling with open source license as default.
|
||||
// The main purpose of the license package is to serve commercial license users to help identify version eligibility
|
||||
// based on purchase date etc.
|
||||
package license
|
||||
|
||||
// Defaults to the open source license.
|
||||
var licenseKey *LicenseKey = MakeOpensourceLicenseKey()
|
||||
|
||||
// Sets and validates the license key.
|
||||
func SetLicenseKey(content string) error {
|
||||
lk, err := licenseKeyDecode(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = lk.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
licenseKey = &lk
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLicenseKey() *LicenseKey {
|
||||
return licenseKey
|
||||
}
|
@ -19,24 +19,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"github.com/unidoc/unidoc/license"
|
||||
. "github.com/unidoc/unidoc/pdf/core"
|
||||
)
|
||||
|
||||
var pdfProducer = ""
|
||||
var pdfCreator = ""
|
||||
|
||||
func getPdfProducer() string {
|
||||
if len(pdfProducer) > 0 {
|
||||
return pdfProducer
|
||||
}
|
||||
|
||||
// We kindly request that users of UniDoc and derived versions refer to UniDoc in the Producer line.
|
||||
// Something like "(based on UniDoc)" would be great.
|
||||
return fmt.Sprintf("UniDoc Library version %s - http://unidoc.io", getUniDocVersion())
|
||||
}
|
||||
|
||||
func SetPdfProducer(producer string) {
|
||||
pdfProducer = producer
|
||||
licenseKey := license.GetLicenseKey()
|
||||
return fmt.Sprintf("UniDoc v%s (%s) - http://unidoc.io", getUniDocVersion(), licenseKey.TypeToString())
|
||||
}
|
||||
|
||||
func getPdfCreator() string {
|
||||
|
Loading…
x
Reference in New Issue
Block a user