github.com/unidoc/unidoc@v2.2.0+incompatible/common/license/crypto.go (about) 1 /* 2 * This file is subject to the terms and conditions defined in 3 * file 'LICENSE.md', which is part of this source code package. 4 */ 5 6 package license 7 8 import ( 9 "crypto" 10 "crypto/rand" 11 "crypto/rsa" 12 "crypto/sha512" 13 "crypto/x509" 14 "encoding/base64" 15 "encoding/json" 16 "encoding/pem" 17 "fmt" 18 "strings" 19 "time" 20 21 _ "github.com/unidoc/unidoc/common" 22 ) 23 24 const ( 25 licenseKeyHeader = "-----BEGIN UNIDOC LICENSE KEY-----" 26 licenseKeyFooter = "-----END UNIDOC LICENSE KEY-----" 27 ) 28 29 // Returns signed content in a base64 format which is in format: 30 // 31 // Base64OriginalContent 32 // + 33 // Base64Signature 34 func signContent(privKey string, content []byte) (string, error) { 35 privBlock, _ := pem.Decode([]byte(privKey)) 36 if privBlock == nil { 37 return "", fmt.Errorf("PrivKey failed") 38 } 39 40 priv, err := x509.ParsePKCS1PrivateKey(privBlock.Bytes) 41 if err != nil { 42 return "", err 43 } 44 45 hash := sha512.New() 46 hash.Write(content) 47 digest := hash.Sum(nil) 48 49 signature, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA512, digest) 50 if err != nil { 51 return "", err 52 } 53 54 ret := base64.StdEncoding.EncodeToString(content) 55 ret += "\n+\n" 56 ret += base64.StdEncoding.EncodeToString(signature) 57 58 return ret, nil 59 } 60 61 // Verifies and reconstructs the original content 62 func verifyContent(pubKey string, content string) ([]byte, error) { 63 // Empty + line is the delimiter between content and signature. 64 // We need to cope with both unix and windows newline, default to unix 65 // one and try Windows one as fallback. 66 separator := "\n+\n" 67 separatorFallback := "\r\n+\r\n" 68 69 sepIdx := strings.Index(content, separator) 70 if sepIdx == -1 { 71 sepIdx = strings.Index(content, separatorFallback) 72 if sepIdx == -1 { 73 return nil, fmt.Errorf("Invalid input, signature separator") 74 } 75 } 76 77 // Original is from start until the separator - 1 78 original := content[:sepIdx] 79 80 // Signature is from after the separator until the end of file. 81 signatureStarts := sepIdx + len(separator) 82 signature := content[signatureStarts:] 83 84 if original == "" || signature == "" { 85 return nil, fmt.Errorf("Invalid input, missing original or signature") 86 } 87 88 originalBytes, err := base64.StdEncoding.DecodeString(original) 89 if err != nil { 90 return nil, fmt.Errorf("Invalid input original") 91 } 92 93 signatureBytes, err := base64.StdEncoding.DecodeString(signature) 94 if err != nil { 95 return nil, fmt.Errorf("Invalid input signature") 96 } 97 98 pubBlock, _ := pem.Decode([]byte(pubKey)) 99 if pubBlock == nil { 100 return nil, fmt.Errorf("PubKey failed") 101 } 102 103 tempPub, err := x509.ParsePKIXPublicKey(pubBlock.Bytes) 104 if err != nil { 105 return nil, err 106 } 107 108 pub := tempPub.(*rsa.PublicKey) 109 if pub == nil { 110 return nil, fmt.Errorf("PubKey conversion failed") 111 } 112 113 hash := sha512.New() 114 hash.Write(originalBytes) 115 digest := hash.Sum(nil) 116 117 err = rsa.VerifyPKCS1v15(pub, crypto.SHA512, digest, signatureBytes) 118 if err != nil { 119 return nil, err 120 } 121 122 return originalBytes, nil 123 } 124 125 // Returns the content wrap around the headers 126 func getWrappedContent(header string, footer string, content string) (string, error) { 127 // Find all content between header and footer. 128 headerIdx := strings.Index(content, header) 129 if headerIdx == -1 { 130 return "", fmt.Errorf("Header not found") 131 } 132 133 footerIdx := strings.Index(content, footer) 134 if footerIdx == -1 { 135 return "", fmt.Errorf("Footer not found") 136 } 137 138 start := headerIdx + len(header) + 1 139 return content[start : footerIdx-1], nil 140 } 141 142 func licenseKeyDecode(content string) (LicenseKey, error) { 143 var ret LicenseKey 144 145 data, err := getWrappedContent(licenseKeyHeader, licenseKeyFooter, content) 146 if err != nil { 147 return ret, err 148 } 149 150 verifiedRet, err := verifyContent(pubKey, data) 151 if err != nil { 152 return ret, err 153 } 154 155 err = json.Unmarshal(verifiedRet, &ret) 156 if err != nil { 157 return ret, err 158 } 159 160 ret.CreatedAt = time.Unix(ret.CreatedAtInt, 0) 161 162 if ret.ExpiresAtInt > 0 { 163 expiresAt := time.Unix(ret.ExpiresAtInt, 0) 164 ret.ExpiresAt = &expiresAt 165 } 166 167 return ret, nil 168 }