MM-16368 - Plugin Signing (#13017)
* [MM-18757] POST handler for `/plugins/marketplace` (#12372) * Implement installMarketplacePlugin * Add InstallMarketplacePlugin endpoint * Fix go.mod * merge with master * Fix go.mod * Fix plugin tests * Move get plugin to marketplace client * Fix stylistic concerns * Add trailing newline to the go.mod * [MM-16586] Add plugin signature settings (#12390) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Update development public key * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Remove init_public_keys string * GolangCI * Closing file handlers * Fixed test that was installing nps plugin * [MM-19798] Implement plugin signature verification (#12768) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Implement plugin signature verification * Remove benburker openpgp * Update en.json * Update development public key * Add support of multiple signatures in filestore * Update en.json * Run go mod vendor * Fix style * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Verify plugin with mattermost public key * Remove init_public_keys string * Add InstallPluginWithSignature method and Refactor * Add signature verification on claster notification * Remove armored signature headers * Add error strings * Fix en.json * Change signatureStorePath * Implement minor fixes * Refactor plugin install methods * Add installPlugin method to uploadPlugin * Update en.json * Refactor installPlugin * Limit number of signatures * Close signatures * Fix helper function * Fix fromReadCloseSeekerToReadSeeker * Cleaned up ReadCloseSeeker for signatures * Remove signature truncation on FS * GolangCI * Add tests for armored signatures and plugin uploads * Fix nil slice issue * Fix TestPluginSync * Fixed tests * Return io.ReadSeeker from downloadFromUrl * Add log for the found plugins in the file store * Remove logging plugin detection info * [MM-20134] Consume and store single-signature for each plugin (#13081) * Consume and store single-signature for each plugin * Fix en.json * Remove saveSignature method * Remove public key hash * PR Feedback * refactored config * PR feedback
Этот коммит содержится в:
132
app/plugin_signature.go
Обычный файл
132
app/plugin_signature.go
Обычный файл
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
// GetPluginPublicKeyFiles returns all public keys listed in the config.
|
||||
func (a *App) GetPluginPublicKeyFiles() ([]string, *model.AppError) {
|
||||
return a.Config().PluginSettings.SignaturePublicKeyFiles, nil
|
||||
}
|
||||
|
||||
// GetPublicKey will return the actual public key saved in the `name` file.
|
||||
func (a *App) GetPublicKey(name string) ([]byte, *model.AppError) {
|
||||
data, err := a.Srv.configStore.GetFile(name)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPublicKey", "app.plugin.get_public_key.get_file.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// AddPublicKey will add plugin public key to the config. Overwrites the previous file
|
||||
func (a *App) AddPublicKey(name string, key io.Reader) *model.AppError {
|
||||
if model.IsSamlFile(&a.Config().SamlSettings, name) {
|
||||
return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
data, err := ioutil.ReadAll(key)
|
||||
if err != nil {
|
||||
return model.NewAppError("AddPublicKey", "app.plugin.write_file.read.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
err = a.Srv.configStore.SetFile(name, data)
|
||||
if err != nil {
|
||||
return model.NewAppError("AddPublicKey", "app.plugin.write_file.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
if !utils.StringInSlice(name, cfg.PluginSettings.SignaturePublicKeyFiles) {
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, name)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePublicKey will delete plugin public key from the config.
|
||||
func (a *App) DeletePublicKey(name string) *model.AppError {
|
||||
if model.IsSamlFile(&a.Config().SamlSettings, name) {
|
||||
return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
filename := filepath.Base(name)
|
||||
if err := a.Srv.configStore.RemoveFile(filename); err != nil {
|
||||
return model.NewAppError("DeletePublicKey", "app.plugin.delete_public_key.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = utils.RemoveStringFromSlice(filename, cfg.PluginSettings.SignaturePublicKeyFiles)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate.
|
||||
func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError {
|
||||
if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil {
|
||||
return nil
|
||||
}
|
||||
publicKeys, appErr := a.GetPluginPublicKeyFiles()
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
for _, pk := range publicKeys {
|
||||
pkBytes, appErr := a.GetPublicKey(pk)
|
||||
if appErr != nil {
|
||||
mlog.Error("Unable to get public key for ", mlog.String("filename", pk))
|
||||
continue
|
||||
}
|
||||
publicKey := bytes.NewReader(pkBytes)
|
||||
plugin.Seek(0, 0)
|
||||
signature.Seek(0, 0)
|
||||
if err := verifySignature(publicKey, plugin, signature); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return model.NewAppError("VerifyPlugin", "api.plugin.verify_plugin.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func verifySignature(publicKey, message, signatrue io.Reader) error {
|
||||
pk, err := decodeIfArmored(publicKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't decode public key")
|
||||
}
|
||||
s, err := decodeIfArmored(signatrue)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't decode signature")
|
||||
}
|
||||
return verifyBinarySignature(pk, message, s)
|
||||
}
|
||||
|
||||
func verifyBinarySignature(publicKey, signedFile, signature io.Reader) error {
|
||||
keyring, err := openpgp.ReadKeyRing(publicKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't read public key")
|
||||
}
|
||||
if _, err = openpgp.CheckDetachedSignature(keyring, signedFile, signature); err != nil {
|
||||
return errors.Wrap(err, "error while checking the signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func decodeIfArmored(reader io.Reader) (io.Reader, error) {
|
||||
readBytes, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't read the file")
|
||||
}
|
||||
block, err := armor.Decode(bytes.NewReader(readBytes))
|
||||
if err != nil {
|
||||
return bytes.NewReader(readBytes), nil
|
||||
}
|
||||
return block.Body, nil
|
||||
}
|
||||
Ссылка в новой задаче
Block a user