blob: 317fcae329e6ac34782ca7035a61e315fe1283df [file] [log] [blame]
// Copyright 2015 The Vanadium 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 impl
import (
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"time"
"v.io/x/ref/services/device/internal/config"
"v.io/x/ref/services/internal/binarylib"
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/services/application"
"v.io/v23/services/repository"
"v.io/v23/verror"
"v.io/x/lib/vlog"
)
// TODO(caprita): Set these timeout in a more principled manner.
const (
childReadyTimeout = 40 * time.Second
childWaitTimeout = 40 * time.Second
rpcContextTimeout = time.Minute
rpcContextLongTimeout = 5 * time.Minute
)
func verifySignature(data []byte, publisher security.Blessings, sig security.Signature) error {
if !publisher.IsZero() {
h := sha256.Sum256(data)
if !sig.Verify(publisher.PublicKey(), h[:]) {
return verror.New(ErrOperationFailed, nil)
}
}
return nil
}
func downloadBinary(ctx *context.T, publisher security.Blessings, bin *application.SignedFile, workspace, fileName string) error {
// TODO(gauthamt): Reduce the number of passes we make over the binary/package
// data to verify its checksum and signature.
data, _, err := binarylib.Download(ctx, bin.File)
if err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Download(%v) failed: %v", bin.File, err))
}
if err := verifySignature(data, publisher, bin.Signature); err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Publisher binary(%v) signature verification failed", bin.File))
}
path, perm := filepath.Join(workspace, fileName), os.FileMode(0755)
if err := ioutil.WriteFile(path, data, perm); err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v, %v) failed: %v", path, perm, err))
}
return nil
}
// TODO(caprita): share code between downloadBinary and downloadPackages.
func downloadPackages(ctx *context.T, publisher security.Blessings, packages application.Packages, pkgDir string) error {
for localPkg, pkgName := range packages {
if localPkg == "" || localPkg[0] == '.' || strings.Contains(localPkg, string(filepath.Separator)) {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("invalid local package name: %q", localPkg))
}
path := filepath.Join(pkgDir, localPkg)
if err := binarylib.DownloadToFile(ctx, pkgName.File, path); err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("DownloadToFile(%q, %q) failed: %v", pkgName, path, err))
}
data, err := ioutil.ReadFile(path)
if err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("ReadPackage(%v) failed: %v", path, err))
}
// If a nonempty signature is present, verify it. (i.e., we accept unsigned packages.)
if !reflect.DeepEqual(pkgName.Signature, security.Signature{}) {
if err := verifySignature(data, publisher, pkgName.Signature); err != nil {
return verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Publisher package(%v:%v) signature verification failed", localPkg, pkgName))
}
}
}
return nil
}
func fetchEnvelope(ctx *context.T, origin string) (*application.Envelope, error) {
stub := repository.ApplicationClient(origin)
profilesSet, err := Describe()
if err != nil {
return nil, verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Failed to obtain profile labels: %v", err))
}
var profiles []string
for label := range profilesSet.Profiles {
profiles = append(profiles, label)
}
envelope, err := stub.Match(ctx, profiles)
if err != nil {
return nil, verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Match(%v) failed: %v", profiles, err))
}
return &envelope, nil
}
// linkSelf creates a link to the current binary.
func linkSelf(workspace, fileName string) error {
path := filepath.Join(workspace, fileName)
self := os.Args[0]
if err := os.Link(self, path); err != nil {
return verror.New(ErrOperationFailed, nil, fmt.Sprintf("Link(%v, %v) failed: %v", self, path, err))
}
return nil
}
func generateVersionDirName() string {
// TODO(caprita): Use generateID instead.
return time.Now().Format(time.RFC3339Nano)
}
func updateLink(target, link string) error {
newLink := link + ".new"
fi, err := os.Lstat(newLink)
if err == nil {
if err := os.Remove(fi.Name()); err != nil {
return verror.New(ErrOperationFailed, nil, fmt.Sprintf("Remove(%v) failed: %v", fi.Name(), err))
}
}
if err := os.Symlink(target, newLink); err != nil {
return verror.New(ErrOperationFailed, nil, fmt.Sprintf("Symlink(%v, %v) failed: %v", target, newLink, err))
}
if err := os.Rename(newLink, link); err != nil {
return verror.New(ErrOperationFailed, nil, fmt.Sprintf("Rename(%v, %v) failed: %v", newLink, link, err))
}
return nil
}
func BaseCleanupDir(path, helper string) {
if helper != "" {
out, err := exec.Command(helper, "--rm", path).CombinedOutput()
if err != nil {
vlog.Errorf("exec.Command(%s %s %s).CombinedOutput() failed: %v", helper, "--rm", path, err)
return
}
if len(out) != 0 {
vlog.Errorf("exec.Command(%s %s %s).CombinedOutput() generated output: %v", helper, "--rm", path, string(out))
}
} else {
if err := os.RemoveAll(path); err != nil {
vlog.Errorf("RemoveAll(%v) failed: %v", path, err)
}
}
}
func PermsDir(c *config.State) string {
return filepath.Join(c.Root, "device-manager", "device-data", "acls")
}
// CleanupDir is defined like this so we can override its implementation for
// tests. CleanupDir will use the helper to delete application state possibly
// owned by different accounts if helper is provided.
var CleanupDir = BaseCleanupDir