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 }