github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/utils/license.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package utils
     5  
     6  import (
     7  	"crypto"
     8  	"crypto/md5"
     9  	"crypto/rsa"
    10  	"crypto/sha512"
    11  	"crypto/x509"
    12  	"encoding/base64"
    13  	"encoding/pem"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"os"
    17  	"strconv"
    18  	"strings"
    19  	"sync/atomic"
    20  
    21  	l4g "github.com/alecthomas/log4go"
    22  
    23  	"github.com/mattermost/mattermost-server/model"
    24  )
    25  
    26  var isLicensedInt32 int32
    27  var licenseValue atomic.Value
    28  var clientLicenseValue atomic.Value
    29  
    30  var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
    31  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
    32  tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N
    33  EZeRhkxyaZW8pLjxwuBUOnXfJew31+gsTNdKZzRjrvPumKr3EtkleuoxNdoatu4E
    34  HrKmR/4Yi71EqAvkhk7ZjQFuF0osSWJMEEGGCSUYQnTEqUzcZSh1BhVpkIkeu8Kk
    35  1wCtptODixvEujgqVe+SrE3UlZjBmPjC/CL+3cYmufpSNgcEJm2mwsdaXp2OPpfn
    36  a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt
    37  hwIDAQAB
    38  -----END PUBLIC KEY-----`)
    39  
    40  func init() {
    41  	SetLicense(nil)
    42  }
    43  
    44  func IsLicensed() bool {
    45  	return atomic.LoadInt32(&isLicensedInt32) == 1
    46  }
    47  
    48  func SetIsLicensed(v bool) {
    49  	if v {
    50  		atomic.StoreInt32(&isLicensedInt32, 1)
    51  	} else {
    52  		atomic.StoreInt32(&isLicensedInt32, 0)
    53  	}
    54  }
    55  
    56  func License() *model.License {
    57  	return licenseValue.Load().(*model.License)
    58  }
    59  
    60  func SetClientLicense(m map[string]string) {
    61  	clientLicenseValue.Store(m)
    62  }
    63  
    64  func ClientLicense() map[string]string {
    65  	return clientLicenseValue.Load().(map[string]string)
    66  }
    67  
    68  func LoadLicense(licenseBytes []byte) {
    69  	if success, licenseStr := ValidateLicense(licenseBytes); success {
    70  		license := model.LicenseFromJson(strings.NewReader(licenseStr))
    71  		SetLicense(license)
    72  		return
    73  	}
    74  
    75  	l4g.Warn(T("utils.license.load_license.invalid.warn"))
    76  }
    77  
    78  var licenseListeners = map[string]func(){}
    79  
    80  func AddLicenseListener(listener func()) string {
    81  	id := model.NewId()
    82  	licenseListeners[id] = listener
    83  	return id
    84  }
    85  
    86  func RemoveLicenseListener(id string) {
    87  	delete(licenseListeners, id)
    88  }
    89  
    90  func SetLicense(license *model.License) bool {
    91  	defer func() {
    92  		for _, listener := range licenseListeners {
    93  			listener()
    94  		}
    95  	}()
    96  
    97  	if license == nil {
    98  		SetIsLicensed(false)
    99  		license = &model.License{
   100  			Features: new(model.Features),
   101  		}
   102  		license.Features.SetDefaults()
   103  		licenseValue.Store(license)
   104  
   105  		SetClientLicense(map[string]string{"IsLicensed": "false"})
   106  
   107  		return false
   108  	} else {
   109  		license.Features.SetDefaults()
   110  
   111  		if !license.IsExpired() {
   112  			licenseValue.Store(license)
   113  			SetIsLicensed(true)
   114  			clientLicenseValue.Store(getClientLicense(license))
   115  			return true
   116  		}
   117  
   118  		return false
   119  	}
   120  }
   121  
   122  func RemoveLicense() {
   123  	SetLicense(nil)
   124  }
   125  
   126  func ValidateLicense(signed []byte) (bool, string) {
   127  	decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed)))
   128  
   129  	_, err := base64.StdEncoding.Decode(decoded, signed)
   130  	if err != nil {
   131  		l4g.Error(T("utils.license.validate_license.decode.error"), err.Error())
   132  		return false, ""
   133  	}
   134  
   135  	if len(decoded) <= 256 {
   136  		l4g.Error(T("utils.license.validate_license.not_long.error"))
   137  		return false, ""
   138  	}
   139  
   140  	// remove null terminator
   141  	for decoded[len(decoded)-1] == byte(0) {
   142  		decoded = decoded[:len(decoded)-1]
   143  	}
   144  
   145  	plaintext := decoded[:len(decoded)-256]
   146  	signature := decoded[len(decoded)-256:]
   147  
   148  	block, _ := pem.Decode(publicKey)
   149  
   150  	public, err := x509.ParsePKIXPublicKey(block.Bytes)
   151  	if err != nil {
   152  		l4g.Error(T("utils.license.validate_license.signing.error"), err.Error())
   153  		return false, ""
   154  	}
   155  
   156  	rsaPublic := public.(*rsa.PublicKey)
   157  
   158  	h := sha512.New()
   159  	h.Write(plaintext)
   160  	d := h.Sum(nil)
   161  
   162  	err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature)
   163  	if err != nil {
   164  		l4g.Error(T("utils.license.validate_license.invalid.error"), err.Error())
   165  		return false, ""
   166  	}
   167  
   168  	return true, string(plaintext)
   169  }
   170  
   171  func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte) {
   172  	fileName := GetLicenseFileLocation(location)
   173  
   174  	if _, err := os.Stat(fileName); err != nil {
   175  		l4g.Debug("We could not find the license key in the database or on disk at %v", fileName)
   176  		return nil, nil
   177  	}
   178  
   179  	l4g.Info("License key has not been uploaded.  Loading license key from disk at %v", fileName)
   180  	licenseBytes := GetLicenseFileFromDisk(fileName)
   181  
   182  	if success, licenseStr := ValidateLicense(licenseBytes); !success {
   183  		l4g.Error("Found license key at %v but it appears to be invalid.", fileName)
   184  		return nil, nil
   185  	} else {
   186  		return model.LicenseFromJson(strings.NewReader(licenseStr)), licenseBytes
   187  	}
   188  }
   189  
   190  func GetLicenseFileFromDisk(fileName string) []byte {
   191  	file, err := os.Open(fileName)
   192  	if err != nil {
   193  		l4g.Error("Failed to open license key from disk at %v err=%v", fileName, err.Error())
   194  		return nil
   195  	}
   196  	defer file.Close()
   197  
   198  	licenseBytes, err := ioutil.ReadAll(file)
   199  	if err != nil {
   200  		l4g.Error("Failed to read license key from disk at %v err=%v", fileName, err.Error())
   201  		return nil
   202  	}
   203  
   204  	return licenseBytes
   205  }
   206  
   207  func GetLicenseFileLocation(fileLocation string) string {
   208  	if fileLocation == "" {
   209  		configDir, _ := FindDir("config")
   210  		return configDir + "mattermost.mattermost-license"
   211  	} else {
   212  		return fileLocation
   213  	}
   214  }
   215  
   216  func getClientLicense(l *model.License) map[string]string {
   217  	props := make(map[string]string)
   218  
   219  	props["IsLicensed"] = strconv.FormatBool(IsLicensed())
   220  
   221  	if IsLicensed() {
   222  		props["Id"] = l.Id
   223  		props["Users"] = strconv.Itoa(*l.Features.Users)
   224  		props["LDAP"] = strconv.FormatBool(*l.Features.LDAP)
   225  		props["MFA"] = strconv.FormatBool(*l.Features.MFA)
   226  		props["SAML"] = strconv.FormatBool(*l.Features.SAML)
   227  		props["Cluster"] = strconv.FormatBool(*l.Features.Cluster)
   228  		props["Metrics"] = strconv.FormatBool(*l.Features.Metrics)
   229  		props["GoogleOAuth"] = strconv.FormatBool(*l.Features.GoogleOAuth)
   230  		props["Office365OAuth"] = strconv.FormatBool(*l.Features.Office365OAuth)
   231  		props["Compliance"] = strconv.FormatBool(*l.Features.Compliance)
   232  		props["CustomBrand"] = strconv.FormatBool(*l.Features.CustomBrand)
   233  		props["MHPNS"] = strconv.FormatBool(*l.Features.MHPNS)
   234  		props["PasswordRequirements"] = strconv.FormatBool(*l.Features.PasswordRequirements)
   235  		props["Announcement"] = strconv.FormatBool(*l.Features.Announcement)
   236  		props["Elasticsearch"] = strconv.FormatBool(*l.Features.Elasticsearch)
   237  		props["DataRetention"] = strconv.FormatBool(*l.Features.DataRetention)
   238  		props["IssuedAt"] = strconv.FormatInt(l.IssuedAt, 10)
   239  		props["StartsAt"] = strconv.FormatInt(l.StartsAt, 10)
   240  		props["ExpiresAt"] = strconv.FormatInt(l.ExpiresAt, 10)
   241  		props["Name"] = l.Customer.Name
   242  		props["Email"] = l.Customer.Email
   243  		props["Company"] = l.Customer.Company
   244  		props["PhoneNumber"] = l.Customer.PhoneNumber
   245  		props["EmailNotificationContents"] = strconv.FormatBool(*l.Features.EmailNotificationContents)
   246  		props["MessageExport"] = strconv.FormatBool(*l.Features.MessageExport)
   247  	}
   248  
   249  	return props
   250  }
   251  
   252  func GetClientLicenseEtag(useSanitized bool) string {
   253  	value := ""
   254  
   255  	lic := ClientLicense()
   256  
   257  	if useSanitized {
   258  		lic = GetSanitizedClientLicense()
   259  	}
   260  
   261  	for k, v := range lic {
   262  		value += fmt.Sprintf("%s:%s;", k, v)
   263  	}
   264  
   265  	return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value))))
   266  }
   267  
   268  func GetSanitizedClientLicense() map[string]string {
   269  	sanitizedLicense := make(map[string]string)
   270  
   271  	for k, v := range ClientLicense() {
   272  		sanitizedLicense[k] = v
   273  	}
   274  
   275  	if IsLicensed() {
   276  		delete(sanitizedLicense, "Id")
   277  		delete(sanitizedLicense, "Name")
   278  		delete(sanitizedLicense, "Email")
   279  		delete(sanitizedLicense, "PhoneNumber")
   280  		delete(sanitizedLicense, "IssuedAt")
   281  		delete(sanitizedLicense, "StartsAt")
   282  		delete(sanitizedLicense, "ExpiresAt")
   283  	}
   284  
   285  	return sanitizedLicense
   286  }