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