// This file is part of arduino-cli. // // Copyright 2020 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, // which covers the main part of arduino-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // // You can be released from the requirements of the above licenses by purchasing // a commercial license. Buying such a license is mandatory if you want to // modify or otherwise use the software for commercial activities involving the // Arduino software without disclosing the source code of your own applications. // To purchase a commercial license, send an email to license@arduino.cc. package resources import ( "bytes" "crypto" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "hash" "io" "io/ioutil" "os" "path/filepath" "strings" paths "github.com/arduino/go-paths-helper" ) // TestLocalArchiveChecksum test if the checksum of the local archive match the checksum of the DownloadResource func (r *DownloadResource) TestLocalArchiveChecksum(downloadDir *paths.Path) (bool, error) { split := strings.SplitN(r.Checksum, ":", 2) if len(split) != 2 { return false, fmt.Errorf("invalid checksum format: %s", r.Checksum) } digest, err := hex.DecodeString(split[1]) if err != nil { return false, fmt.Errorf("invalid hash '%s': %s", split[1], err) } // names based on: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest var algo hash.Hash switch split[0] { case "SHA-256": algo = crypto.SHA256.New() case "SHA-1": algo = crypto.SHA1.New() case "MD5": algo = crypto.MD5.New() default: return false, fmt.Errorf("unsupported hash algorithm: %s", split[0]) } filePath, err := r.ArchivePath(downloadDir) if err != nil { return false, fmt.Errorf("getting archive path: %s", err) } file, err := os.Open(filePath.String()) if err != nil { return false, fmt.Errorf("opening archive file: %s", err) } defer file.Close() if _, err := io.Copy(algo, file); err != nil { return false, fmt.Errorf("computing hash: %s", err) } return bytes.Compare(algo.Sum(nil), digest) == 0, nil } // TestLocalArchiveSize test if the local archive size match the DownloadResource size func (r *DownloadResource) TestLocalArchiveSize(downloadDir *paths.Path) (bool, error) { filePath, err := r.ArchivePath(downloadDir) if err != nil { return false, fmt.Errorf("getting archive path: %s", err) } info, err := filePath.Stat() if err != nil { return false, fmt.Errorf("getting archive info: %s", err) } return info.Size() == r.Size, nil } // TestLocalArchiveIntegrity checks for integrity of the local archive. func (r *DownloadResource) TestLocalArchiveIntegrity(downloadDir *paths.Path) (bool, error) { if cached, err := r.IsCached(downloadDir); err != nil { return false, fmt.Errorf("testing if archive is cached: %s", err) } else if !cached { return false, nil } if ok, err := r.TestLocalArchiveSize(downloadDir); err != nil { return false, fmt.Errorf("teting archive size: %s", err) } else if !ok { return false, nil } ok, err := r.TestLocalArchiveChecksum(downloadDir) if err != nil { return false, fmt.Errorf("testing archive checksum: %s", err) } return ok, nil } const ( filePermissions = 0644 packageFileName = "package.json" ) type packageFile struct { Checksum string `json:"checksum"` } func computeDirChecksum(root string) (string, error) { hash := sha256.New() err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || (info.Name() == packageFileName && filepath.Dir(path) == root) { return nil } f, err := os.Open(path) if err != nil { return nil } defer f.Close() if _, err := io.Copy(hash, f); err != nil { return fmt.Errorf("failed to compute hash of file \"%s\"", info.Name()) } return nil }) if err != nil { return "", err } return fmt.Sprintf("%x", hash.Sum(nil)), nil } func createPackageFile(root string) error { checksum, err := computeDirChecksum(root) if err != nil { return err } packageJSON, _ := json.Marshal(packageFile{checksum}) err = ioutil.WriteFile(filepath.Join(root, packageFileName), packageJSON, filePermissions) if err != nil { return err } return nil } // CheckDirChecksum reads checksum from the package.json and compares it with a recomputed value. func CheckDirChecksum(root string) (bool, error) { packageJSON, err := ioutil.ReadFile(filepath.Join(root, packageFileName)) if err != nil { return false, err } var file packageFile json.Unmarshal(packageJSON, &file) checksum, err := computeDirChecksum(root) if err != nil { return false, err } return file.Checksum == checksum, nil }