mirror of https://github.com/poanetwork/quorum.git
174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
package plugin
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/pborman/uuid"
|
|
"golang.org/x/crypto/openpgp"
|
|
)
|
|
|
|
// Returns true if provided string contains only alphanumeric characters with the exception of the character (.) otherwise false.
|
|
func isCleanFileName(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
return regexp.MustCompile(`^[\w.-]+$`).MatchString(s)
|
|
}
|
|
|
|
// Returns true if provided string contains only alphanumeric characters otherwise false
|
|
func isCleanEntryPoint(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
return regexp.MustCompile(`^[\w-_.]+$`).MatchString(s)
|
|
|
|
}
|
|
|
|
func unzipFile(output string, input *zip.File) error {
|
|
inputFile, err := input.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
_ = inputFile.Close()
|
|
}()
|
|
outputFile, err := os.OpenFile(
|
|
output,
|
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
|
input.Mode(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
_ = outputFile.Close()
|
|
}()
|
|
_, err = io.Copy(outputFile, inputFile)
|
|
return err
|
|
}
|
|
|
|
// Unzip src path to dest. Creates dest if the file doesnt exists.
|
|
func unzip(src string, dest string) error {
|
|
zipReader, err := zip.OpenReader(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetDir := dest
|
|
for _, file := range zipReader.Reader.File {
|
|
extractedFilePath := filepath.Join(
|
|
targetDir,
|
|
file.Name,
|
|
)
|
|
if file.FileInfo().IsDir() {
|
|
if err := os.MkdirAll(extractedFilePath, file.Mode()); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := unzipFile(extractedFilePath, file); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Returns Hex encoded value of sha256(binary content of filepath)
|
|
func getSha256Checksum(filePath string) (string, error) {
|
|
//Open the passed argument and check for any error
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
//Open a new hash interface to write to
|
|
hash := sha256.New()
|
|
if _, err := io.Copy(hash, file); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
|
}
|
|
|
|
// Unpack pluginPath and returns plugin random generated unpacking path & plugin metadata.
|
|
func unpackPlugin(pluginPath string) (string, *MetaData, error) {
|
|
// Unpack pluginMeta
|
|
// Reduce TOC/TOU risk
|
|
unpackDir := path.Join(os.TempDir(), uuid.New(), uuid.New())
|
|
|
|
err := os.MkdirAll(unpackDir, os.ModePerm)
|
|
if err != nil {
|
|
return unpackDir, nil, err
|
|
}
|
|
|
|
// Unzip to new Dir
|
|
err = unzip(pluginPath, unpackDir)
|
|
if err != nil {
|
|
return unpackDir, nil, err
|
|
}
|
|
|
|
// Make Plugin
|
|
pluginMeta := MetaData{}
|
|
// Verify Plugin Structure
|
|
jsonFile, err := os.Open(path.Join(unpackDir, "plugin-meta.json"))
|
|
if err != nil {
|
|
return unpackDir, nil, err
|
|
}
|
|
defer jsonFile.Close()
|
|
|
|
if err := json.NewDecoder(jsonFile).Decode(&pluginMeta); err != nil {
|
|
return unpackDir, nil, err
|
|
}
|
|
|
|
if pluginMeta.EntryPoint == "" {
|
|
return unpackDir, nil, fmt.Errorf("plugin-meta.json entry point not set")
|
|
}
|
|
|
|
if !isCleanEntryPoint(pluginMeta.EntryPoint) {
|
|
return unpackDir, nil, fmt.Errorf("entrypoint must be only alphanumeric value")
|
|
}
|
|
return unpackDir, &pluginMeta, nil
|
|
}
|
|
|
|
func verify(signature, pubkey []byte, checksum string) error {
|
|
// verify file signature
|
|
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(pubkey))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entity, err := openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum), bytes.NewReader(signature))
|
|
if err != nil {
|
|
log.Debug("unable to verify signature with original checksum. Now add \\n to the end and try", "checksum", checksum, "error", err)
|
|
entity, err = openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum+"\n"), bytes.NewReader(signature))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if entity == nil {
|
|
return fmt.Errorf("verification failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resolve URL-based value to file path
|
|
func resolveFilePath(rawUrl string) (string, error) {
|
|
u, err := url.Parse(rawUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Abs(filepath.Join(u.Host, u.Path))
|
|
}
|