github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/plugin/utils.go (about)

     1  package plugin
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/url"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  
    18  	"github.com/kisexp/xdchain/log"
    19  	"github.com/pborman/uuid"
    20  	"golang.org/x/crypto/openpgp"
    21  )
    22  
    23  // Returns true if provided string contains only alphanumeric characters with the exception of the character (.) otherwise false.
    24  func isCleanFileName(s string) bool {
    25  	if s == "" {
    26  		return false
    27  	}
    28  	return regexp.MustCompile(`^[\w.-]+$`).MatchString(s)
    29  }
    30  
    31  // Returns true if provided string contains only alphanumeric characters otherwise false
    32  func isCleanEntryPoint(s string) bool {
    33  	if s == "" {
    34  		return false
    35  	}
    36  	return regexp.MustCompile(`^[\w-_.]+$`).MatchString(s)
    37  
    38  }
    39  
    40  func unzipFile(output string, input *zip.File) error {
    41  	inputFile, err := input.Open()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	defer func() {
    46  		_ = inputFile.Close()
    47  	}()
    48  	outputFile, err := os.OpenFile(
    49  		output,
    50  		os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
    51  		input.Mode(),
    52  	)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	defer func() {
    57  		_ = outputFile.Close()
    58  	}()
    59  	_, err = io.Copy(outputFile, inputFile)
    60  	return err
    61  }
    62  
    63  // Unzip src path to dest. Creates dest if the file doesnt exists.
    64  func unzip(src string, dest string) error {
    65  	zipReader, err := zip.OpenReader(src)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	targetDir := dest
    70  	for _, file := range zipReader.Reader.File {
    71  		extractedFilePath := filepath.Join(
    72  			targetDir,
    73  			file.Name,
    74  		)
    75  		if file.FileInfo().IsDir() {
    76  			if err := os.MkdirAll(extractedFilePath, file.Mode()); err != nil {
    77  				return err
    78  			}
    79  		} else {
    80  			if err := unzipFile(extractedFilePath, file); err != nil {
    81  				return err
    82  			}
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  // Returns Hex encoded value of sha256(binary content of filepath)
    89  func getSha256Checksum(filePath string) (string, error) {
    90  	//Open the passed argument and check for any error
    91  	file, err := os.Open(filePath)
    92  	if err != nil {
    93  		return "", err
    94  	}
    95  	defer file.Close()
    96  
    97  	//Open a new hash interface to write to
    98  	hash := sha256.New()
    99  	if _, err := io.Copy(hash, file); err != nil {
   100  		return "", err
   101  	}
   102  
   103  	return hex.EncodeToString(hash.Sum(nil)), nil
   104  }
   105  
   106  // Unpack pluginPath and returns plugin random generated unpacking path & plugin metadata.
   107  func unpackPlugin(pluginPath string) (string, *MetaData, error) {
   108  	// Unpack pluginMeta
   109  	// Reduce TOC/TOU risk
   110  	unpackDir := path.Join(os.TempDir(), uuid.New(), uuid.New())
   111  
   112  	err := os.MkdirAll(unpackDir, os.ModePerm)
   113  	if err != nil {
   114  		return unpackDir, nil, err
   115  	}
   116  
   117  	// Unzip to new Dir
   118  	err = unzip(pluginPath, unpackDir)
   119  	if err != nil {
   120  		return unpackDir, nil, err
   121  	}
   122  
   123  	// Make Plugin
   124  	pluginMeta := MetaData{}
   125  	// Verify Plugin Structure
   126  	jsonFile, err := os.Open(path.Join(unpackDir, "plugin-meta.json"))
   127  	if err != nil {
   128  		return unpackDir, nil, err
   129  	}
   130  	defer jsonFile.Close()
   131  
   132  	if err := json.NewDecoder(jsonFile).Decode(&pluginMeta); err != nil {
   133  		return unpackDir, nil, err
   134  	}
   135  
   136  	if pluginMeta.EntryPoint == "" {
   137  		return unpackDir, nil, fmt.Errorf("plugin-meta.json entry point not set")
   138  	}
   139  
   140  	if !isCleanEntryPoint(pluginMeta.EntryPoint) {
   141  		return unpackDir, nil, fmt.Errorf("entrypoint must be only alphanumeric value")
   142  	}
   143  	return unpackDir, &pluginMeta, nil
   144  }
   145  
   146  func verify(signature, pubkey []byte, checksum string) error {
   147  	// verify file signature
   148  	keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(pubkey))
   149  	if err != nil {
   150  		return err
   151  	}
   152  	entity, err := openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum), bytes.NewReader(signature))
   153  	if err != nil {
   154  		log.Debug("unable to verify signature with original checksum. Now add \\n to the end and try", "checksum", checksum, "error", err)
   155  		entity, err = openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum+"\n"), bytes.NewReader(signature))
   156  		if err != nil {
   157  			return err
   158  		}
   159  	}
   160  	if entity == nil {
   161  		return fmt.Errorf("verification failed")
   162  	}
   163  	return nil
   164  }
   165  
   166  // resolve URL-based value to file path
   167  func resolveFilePath(rawUrl string) (string, error) {
   168  	u, err := url.Parse(rawUrl)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	return filepath.Abs(filepath.Join(u.Host, u.Path))
   173  }