github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_signature.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"path/filepath"
    12  
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/crypto/openpgp"
    15  	"golang.org/x/crypto/openpgp/armor"
    16  
    17  	"github.com/mattermost/mattermost-server/v5/mlog"
    18  	"github.com/mattermost/mattermost-server/v5/model"
    19  	"github.com/mattermost/mattermost-server/v5/utils"
    20  )
    21  
    22  // GetPluginPublicKeyFiles returns all public keys listed in the config.
    23  func (a *App) GetPluginPublicKeyFiles() ([]string, *model.AppError) {
    24  	return a.Config().PluginSettings.SignaturePublicKeyFiles, nil
    25  }
    26  
    27  // GetPublicKey will return the actual public key saved in the `name` file.
    28  func (a *App) GetPublicKey(name string) ([]byte, *model.AppError) {
    29  	data, err := a.Srv().configStore.GetFile(name)
    30  	if err != nil {
    31  		return nil, model.NewAppError("GetPublicKey", "app.plugin.get_public_key.get_file.app_error", nil, err.Error(), http.StatusInternalServerError)
    32  	}
    33  	return data, nil
    34  }
    35  
    36  // AddPublicKey will add plugin public key to the config. Overwrites the previous file
    37  func (a *App) AddPublicKey(name string, key io.Reader) *model.AppError {
    38  	if model.IsSamlFile(&a.Config().SamlSettings, name) {
    39  		return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
    40  	}
    41  	data, err := ioutil.ReadAll(key)
    42  	if err != nil {
    43  		return model.NewAppError("AddPublicKey", "app.plugin.write_file.read.app_error", nil, err.Error(), http.StatusInternalServerError)
    44  	}
    45  	err = a.Srv().configStore.SetFile(name, data)
    46  	if err != nil {
    47  		return model.NewAppError("AddPublicKey", "app.plugin.write_file.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
    48  	}
    49  
    50  	a.UpdateConfig(func(cfg *model.Config) {
    51  		if !utils.StringInSlice(name, cfg.PluginSettings.SignaturePublicKeyFiles) {
    52  			cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, name)
    53  		}
    54  	})
    55  
    56  	return nil
    57  }
    58  
    59  // DeletePublicKey will delete plugin public key from the config.
    60  func (a *App) DeletePublicKey(name string) *model.AppError {
    61  	if model.IsSamlFile(&a.Config().SamlSettings, name) {
    62  		return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
    63  	}
    64  	filename := filepath.Base(name)
    65  	if err := a.Srv().configStore.RemoveFile(filename); err != nil {
    66  		return model.NewAppError("DeletePublicKey", "app.plugin.delete_public_key.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
    67  	}
    68  
    69  	a.UpdateConfig(func(cfg *model.Config) {
    70  		cfg.PluginSettings.SignaturePublicKeyFiles = utils.RemoveStringFromSlice(filename, cfg.PluginSettings.SignaturePublicKeyFiles)
    71  	})
    72  
    73  	return nil
    74  }
    75  
    76  // VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate.
    77  func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError {
    78  	if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil {
    79  		return nil
    80  	}
    81  	publicKeys, appErr := a.GetPluginPublicKeyFiles()
    82  	if appErr != nil {
    83  		return appErr
    84  	}
    85  	for _, pk := range publicKeys {
    86  		pkBytes, appErr := a.GetPublicKey(pk)
    87  		if appErr != nil {
    88  			mlog.Warn("Unable to get public key for ", mlog.String("filename", pk))
    89  			continue
    90  		}
    91  		publicKey := bytes.NewReader(pkBytes)
    92  		plugin.Seek(0, 0)
    93  		signature.Seek(0, 0)
    94  		if err := verifySignature(publicKey, plugin, signature); err == nil {
    95  			return nil
    96  		}
    97  	}
    98  	return model.NewAppError("VerifyPlugin", "api.plugin.verify_plugin.app_error", nil, "", http.StatusInternalServerError)
    99  }
   100  
   101  func verifySignature(publicKey, message, signatrue io.Reader) error {
   102  	pk, err := decodeIfArmored(publicKey)
   103  	if err != nil {
   104  		return errors.Wrap(err, "can't decode public key")
   105  	}
   106  	s, err := decodeIfArmored(signatrue)
   107  	if err != nil {
   108  		return errors.Wrap(err, "can't decode signature")
   109  	}
   110  	return verifyBinarySignature(pk, message, s)
   111  }
   112  
   113  func verifyBinarySignature(publicKey, signedFile, signature io.Reader) error {
   114  	keyring, err := openpgp.ReadKeyRing(publicKey)
   115  	if err != nil {
   116  		return errors.Wrap(err, "can't read public key")
   117  	}
   118  	if _, err = openpgp.CheckDetachedSignature(keyring, signedFile, signature); err != nil {
   119  		return errors.Wrap(err, "error while checking the signature")
   120  	}
   121  	return nil
   122  }
   123  
   124  func decodeIfArmored(reader io.Reader) (io.Reader, error) {
   125  	readBytes, err := ioutil.ReadAll(reader)
   126  	if err != nil {
   127  		return nil, errors.Wrap(err, "can't read the file")
   128  	}
   129  	block, err := armor.Decode(bytes.NewReader(readBytes))
   130  	if err != nil {
   131  		return bytes.NewReader(readBytes), nil
   132  	}
   133  	return block.Body, nil
   134  }