Files
mostlymatter/app/plugin_signature.go
Agniva De Sarker dbf779b828 MM-40813: Add config interface (#19571)
We create the basic boilerplate to pass services
to each product. The basic idea is to have a map
containing service names and the services.

The server will pass all services to all products.
But it is on the product, to choose what services
they will actually consume.

Each product will cast the received service to
an interface with the methods that product requires.

https://mattermost.atlassian.net/browse/MM-40813

```release-note
NONE
```
2022-02-21 21:41:04 +05:30

140 строки
4.9 KiB
Go

// 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/pkg/errors"
"golang.org/x/crypto/openpgp" //nolint:staticcheck
"golang.org/x/crypto/openpgp/armor" //nolint:staticcheck
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/utils"
)
// GetPublicKey will return the actual public key saved in the `name` file.
func (a *App) GetPublicKey(name string) ([]byte, *model.AppError) {
return a.Srv().getPublicKey(name)
}
func (s *Server) getPublicKey(name string) ([]byte, *model.AppError) {
data, err := s.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 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 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 {
return a.ch.verifyPlugin(plugin, signature)
}
func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError {
if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil {
return nil
}
publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles
for _, pk := range publicKeys {
pkBytes, appErr := ch.srv.getPublicKey(pk)
if appErr != nil {
mlog.Warn("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
}
// isSamlFile checks if filename is a SAML file.
func isSamlFile(saml *model.SamlSettings, filename string) bool {
return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile
}