package plugins import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io" "io/ioutil" "os" "path/filepath" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/util/errutil" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/clearsign" ) // Soon we can fetch keys from: // https://grafana.com/api/plugins/ci/keys const publicKeyText = `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: OpenPGP.js v4.10.1 Comment: https://openpgpjs.org xpMEXpTXXxMFK4EEACMEIwQBiOUQhvGbDLvndE0fEXaR0908wXzPGFpf0P0Z HJ06tsq+0higIYHp7WTNJVEZtcwoYLcPRGaa9OQqbUU63BEyZdgAkPTz3RFd 5+TkDWZizDcaVFhzbDd500yTwexrpIrdInwC/jrgs7Zy/15h8KA59XXUkdmT YB6TR+OA9RKME+dCJozNGUdyYWZhbmEgPGVuZ0BncmFmYW5hLmNvbT7CvAQQ EwoAIAUCXpTXXwYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BAAoJEH5NDGpw iGbnaWoCCQGQ3SQnCkRWrG6XrMkXOKfDTX2ow9fuoErN46BeKmLM4f1EkDZQ Tpq3SE8+My8B5BIH3SOcBeKzi3S57JHGBdFA+wIJAYWMrJNIvw8GeXne+oUo NzzACdvfqXAZEp/HFMQhCKfEoWGJE8d2YmwY2+3GufVRTI5lQnZOHLE8L/Vc 1S5MXESjzpcEXpTXXxIFK4EEACMEIwQBtHX/SD5Qm3v4V92qpaIZQgtTX0sT cFPjYWAHqsQ1iENrYN/vg1wU3ADlYATvydOQYvkTyT/tbDvx2Fse8PL84MQA YKKQ6AJ3gLVvmeouZdU03YoV4MYaT8KbnJUkZQZkqdz2riOlySNI9CG3oYmv omjUAtzgAgnCcurfGLZkkMxlmY8DAQoJwqQEGBMKAAkFAl6U118CGwwACgkQ fk0ManCIZuc0jAIJAVw2xdLr4ZQqPUhubrUyFcqlWoW8dQoQagwO8s8ubmby KuLA9FWJkfuuRQr+O9gHkDVCez3aism7zmJBqIOi38aNAgjJ3bo6leSS2jR/ x5NqiKVi83tiXDPncDQYPymOnMhW0l7CVA7wj75HrFvvlRI/4MArlbsZ2tBn N1c5v9v/4h6qeA== =DNbR -----END PGP PUBLIC KEY BLOCK----- ` // pluginManifest holds details for the file manifest type pluginManifest struct { Plugin string `json:"plugin"` Version string `json:"version"` KeyID string `json:"keyId"` Time int64 `json:"time"` Files map[string]string `json:"files"` } // readPluginManifest attempts to read and verify the plugin manifest // if any error occurs or the manifest is not valid, this will return an error func readPluginManifest(body []byte) (*pluginManifest, error) { block, _ := clearsign.Decode(body) if block == nil { return nil, errors.New("unable to decode manifest") } // Convert to a well typed object manifest := &pluginManifest{} err := json.Unmarshal(block.Plaintext, &manifest) if err != nil { return nil, errutil.Wrap("Error parsing manifest JSON", err) } keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKeyText)) if err != nil { return nil, errutil.Wrap("failed to parse public key", err) } if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(block.Bytes), block.ArmoredSignature.Body); err != nil { return nil, errutil.Wrap("failed to check signature", err) } return manifest, nil } // getPluginSignatureState returns the signature state for a plugin. func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature { log.Debug("Getting signature state of plugin", "plugin", plugin.Id, "isBackend", plugin.Backend) manifestPath := filepath.Join(plugin.PluginDir, "MANIFEST.txt") byteValue, err := ioutil.ReadFile(manifestPath) if err != nil || len(byteValue) < 10 { log.Debug("Plugin is unsigned", "id", plugin.Id) return PluginSignatureUnsigned } manifest, err := readPluginManifest(byteValue) if err != nil { log.Debug("Plugin signature invalid", "id", plugin.Id) return PluginSignatureInvalid } // Make sure the versions all match if manifest.Plugin != plugin.Id || manifest.Version != plugin.Info.Version { return PluginSignatureModified } // Verify the manifest contents log.Debug("Verifying contents of plugin manifest", "plugin", plugin.Id) for p, hash := range manifest.Files { // Open the file fp := filepath.Join(plugin.PluginDir, p) f, err := os.Open(fp) if err != nil { return PluginSignatureModified } defer f.Close() h := sha256.New() if _, err := io.Copy(h, f); err != nil { log.Warn("Couldn't read plugin file", "plugin", plugin.Id, "filename", fp) return PluginSignatureModified } sum := hex.EncodeToString(h.Sum(nil)) if sum != hash { log.Warn("Plugin file's signature has been modified versus manifest", "plugin", plugin.Id, "filename", fp) return PluginSignatureModified } } // Everything OK log.Debug("Plugin signature valid", "id", plugin.Id) return PluginSignatureValid }