blob: fab4d5d1becbf11279ba1280eea2e85bbde207a6 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package publish
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
tuf "github.com/flynn/go-tuf"
)
type targetsFile struct {
Signed struct {
Targets map[string]struct {
Custom struct {
Merkle string `json:"merkle"`
} `json:"custom"`
} `json:"targets"`
} `json:"signed"`
}
var merklePat = regexp.MustCompile("^[0-9a-f]{64}$")
func TestCopyFile(t *testing.T) {
fileSize := 8193
src := writeRandFile(t, fileSize)
defer os.Remove(src)
fileContent, err := ioutil.ReadFile(src)
if err != nil {
t.Fatal(err)
}
dst := filepath.Join(os.TempDir(), fmt.Sprintf("publish-test-dest-%x", fileContent[fileSize-8:]))
defer os.Remove(dst)
if err := copyFile(dst, src); err != nil {
t.Fatalf("File copy errored out! %v", err)
}
fInfo, err := os.Stat(dst)
if err != nil || fInfo.Size() != int64(fileSize) {
t.Fatalf("Copied file does not exist or is wrong size")
}
copyContent, err := ioutil.ReadFile(dst)
if err != nil {
t.Fatal(err)
}
if len(copyContent) != fileSize || err != nil {
t.Fatalf("Copied file could not be read")
}
if bytes.Compare(copyContent, fileContent) != 0 {
t.Fatalf("Copied file content does not match")
}
}
func TestCopyMakingDirs(t *testing.T) {
fileSize := 8193
src := writeRandFile(t, fileSize)
defer os.Remove(src)
nonPath := filepath.Join(os.TempDir(), "abc", "789")
defer os.Remove(filepath.Join(os.TempDir(), "abc"))
defer os.Remove(filepath.Join(os.TempDir(), "abc", "789"))
err := copyFile(nonPath, src)
if err != nil {
t.Fatalf("Non-existent path rejected when requesting directory creation")
}
}
func TestCopyInvalidSrcDst(t *testing.T) {
dst, err := ioutil.TempFile("", "publish-test-file")
if err != nil {
t.Fatalf("Could not create temporary destiation file")
}
dst.Close()
defer os.Remove(dst.Name())
// try making the source file a directory
srcDir, err := ioutil.TempDir("", "publish-test-srcdir")
if err != nil {
t.Fatalf("Could not create temporary directory")
}
defer os.Remove(srcDir)
err = copyFile(dst.Name(), srcDir)
if err == nil {
t.Fatalf("No error returned from copy")
}
if err != os.ErrInvalid {
t.Fatalf("Copy rejected directory as source, but returned unexpected error %v", err)
}
err = copyFile(srcDir, dst.Name())
if err == nil {
t.Fatalf("Copy accepted directory as destination")
}
}
func TestPopulateKeys(t *testing.T) {
srcDir := createFakeKeys(t)
defer os.RemoveAll(srcDir)
dstDir, err := ioutil.TempDir("", "publish-test-keys-dst")
if err != nil {
t.Fatalf("Couldn't create test destination directory.")
}
defer os.RemoveAll(dstDir)
err = populateKeys(dstDir, srcDir)
if err != nil {
t.Fatalf("Error returned from populate keys: %v", err)
}
for _, k := range keySet {
_, err := os.Stat(filepath.Join(dstDir, k))
if err != nil {
t.Fatalf("Couldn't stat destination file '%s' %v", k, err)
}
}
}
func TestInitRepo(t *testing.T) {
keysPath, err := ioutil.TempDir("", "publish-test-keys")
if err != nil {
t.Fatalf("Couldn't creating test directory %v", err)
}
defer os.RemoveAll(keysPath)
genKeys(keysPath, t)
repoDir, err := ioutil.TempDir("", "publish-test-repo")
if err != nil {
t.Fatalf("Couldn't create test repo directory.")
}
defer os.RemoveAll(repoDir)
checkPaths := []string{filepath.Join(repoDir, "repository", "root.json")}
for _, k := range keySet {
checkPaths = append(checkPaths, filepath.Join(repoDir, "keys", k))
}
_, err = InitRepo(repoDir, keysPath)
if err != nil {
t.Fatalf("Repo init returned error %v", err)
}
for _, path := range checkPaths {
_, err := os.Stat(path)
if err != nil {
t.Fatalf("Expected path '%s' had error %v", path, err)
}
}
}
func TestAddTUFFile(t *testing.T) {
keysPath, err := ioutil.TempDir("", "publish-test-keys")
if err != nil {
t.Fatalf("Couldn't creating test directory %v", err)
}
defer os.RemoveAll(keysPath)
genKeys(keysPath, t)
repoDir, err := ioutil.TempDir("", "publish-test-repo")
if err != nil {
t.Fatalf("Couldn't create test repo directory.")
}
defer os.RemoveAll(repoDir)
checkPaths := []string{filepath.Join(repoDir, "repository", "root.json")}
for _, k := range keySet {
checkPaths = append(checkPaths, filepath.Join(repoDir, "keys", k))
}
amberRepo, err := InitRepo(repoDir, keysPath)
if err != nil {
t.Fatalf("Repo init returned error %v", err)
}
targetName := "test-test"
err = amberRepo.AddPackage("test-test", io.LimitReader(rand.Reader, 8193))
if err != nil {
t.Fatalf("Problem adding repo file %v", err)
}
if err = amberRepo.CommitUpdates(true); err != nil {
t.Fatalf("Failure commiting update %s", err)
}
contents, err := os.Open(filepath.Join(repoDir, "repository", "targets"))
if err != nil {
t.Fatalf("Unable to read targets directory %v", err)
}
found := false
contentList, err := contents.Readdir(0)
if err != nil {
t.Fatalf("Couldn't read targets directory")
}
for _, info := range contentList {
n := info.Name()
if strings.Contains(n, targetName) &&
strings.LastIndex(n, targetName) == len(n)-len(targetName) {
found = true
break
}
}
if !found {
t.Fatalf("Didn't find expected file")
}
// do a basic sanity check that a merkle element is included in the
// targets field
targs, err := os.Open(filepath.Join(repoDir, "repository", "targets.json"))
if err != nil {
t.Fatalf("Couldn't open targets metadata %v", err)
}
defer targs.Close()
var targets targetsFile
decoder := json.NewDecoder(targs)
err = decoder.Decode(&targets)
if err != nil {
t.Fatalf("Couldn't decode targets metadata %v", err)
}
if len(targets.Signed.Targets) == 0 {
t.Fatalf("Targets file contains no targets")
}
for _, target := range targets.Signed.Targets {
if !merklePat.MatchString(target.Custom.Merkle) {
t.Fatalf("Targets JSON contains invalid merkle entry: %v", target.Custom.Merkle)
}
}
}
func TestAddBlob(t *testing.T) {
keysPath, err := ioutil.TempDir("", "publish-test-keys")
if err != nil {
t.Fatalf("Couldn't creating test directory %v", err)
}
defer os.RemoveAll(keysPath)
genKeys(keysPath, t)
repoDir, err := ioutil.TempDir("", "publish-test-repo")
if err != nil {
t.Fatalf("Couldn't create test repo directory.")
}
defer os.RemoveAll(repoDir)
checkPaths := []string{filepath.Join(repoDir, "repository", "root.json")}
for _, k := range keySet {
checkPaths = append(checkPaths, filepath.Join(repoDir, "keys", k))
}
repo, err := InitRepo(repoDir, keysPath)
if err != nil {
t.Fatalf("Repo init returned error %v", err)
}
defer os.RemoveAll(repoDir)
repo.AddBlob("", io.LimitReader(rand.Reader, 8193))
blobs, err := os.Open(filepath.Join(repoDir, "repository", "blobs"))
if err != nil {
t.Fatalf("Couldn't open blobs directory for reading %v", err)
}
defer blobs.Close()
files, err := blobs.Readdir(0)
if err != nil {
t.Fatalf("Error reading blobs directory %v", err)
}
if len(files) != 1 {
t.Fatalf("Unexpected number of blobs in blobs directory")
}
// TODO(jmatt) Verify name is merkle root?
}
func writeRandFile(t *testing.T, size int) string {
dst, err := ioutil.TempFile("", "publish-test")
if err != nil {
t.Fatalf("Could not make temporary file, %v", err)
}
defer dst.Close()
if _, err = io.Copy(dst, io.LimitReader(rand.Reader, int64(size))); err != nil {
t.Fatalf("Unable to write to temp file %v", err)
}
return dst.Name()
}
func createFakeKeys(t *testing.T) string {
srcDir, err := ioutil.TempDir("", "publish-test-keys-src")
if err != nil {
t.Fatalf("Couldn't create test source directory.")
}
for _, k := range keySet {
f, err := os.OpenFile(filepath.Join(srcDir, k), os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("Unable to create source file '%s' %v", k, err)
}
f.Close()
}
return srcDir
}
// genKeys uses go-tuf client methods directly to create a set of keys which
// our wrapper client is expected to ingest
func genKeys(keysDst string, t *testing.T) {
// use the TUF library directly to create a set of keys and empty root
// manifest
storePath, err := ioutil.TempDir("", "publish-test-genkeys")
if err != nil {
t.Fatalf("Couldn't creating test directory %v", err)
}
defer os.RemoveAll(storePath)
store := tuf.FileSystemStore(storePath, func(role string, confirm bool) ([]byte, error) { return []byte(""), nil })
tufRepo, err := tuf.NewRepo(store, "sha512")
if err != nil {
t.Fatalf("Repository couldn't be created")
}
err = tufRepo.Init(true)
if err != nil {
t.Fatalf("Error initializing repository %v", err)
}
// create all the keys
for _, k := range []string{"root", "timestamp", "targets", "snapshot"} {
_, err = tufRepo.GenKey(k)
if err != nil {
fmt.Printf("Error creating key %s: %s\n", k, err)
}
filename := k + ".json"
err = copyFile(filepath.Join(keysDst, filename),
filepath.Join(storePath, "keys", filename))
if err != nil {
t.Fatalf("Failed to copy key to output path %v", err)
}
}
// copy the root.json, which is the manifest for the empty repo into the keys
// directory. The InitRepo method will want to ingest this.
err = copyFile(filepath.Join(keysDst, rootManifest),
filepath.Join(storePath, "staged", "root.json"))
if err != nil {
t.Fatalf("Couldn't copy root json manifest %v", err)
}
}