From 2cccbbe1d8b1d870a28e20f92366accae7a2aa00 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 14 Aug 2020 20:31:37 +0000 Subject: [PATCH] update: add initial update command powered by equinox --- cmd/root.go | 2 + cmd/update/update.go | 57 +++ go.mod | 1 + go.sum | 2 + release | 7 + .../github.com/equinox-io/equinox/.travis.yml | 13 + vendor/github.com/equinox-io/equinox/LICENSE | 21 + .../github.com/equinox-io/equinox/README.md | 99 +++++ vendor/github.com/equinox-io/equinox/doc.go | 92 ++++ vendor/github.com/equinox-io/equinox/go.mod | 1 + .../equinox/internal/go-update/LICENSE | 13 + .../equinox/internal/go-update/README.md | 65 +++ .../equinox/internal/go-update/apply.go | 322 ++++++++++++++ .../equinox/internal/go-update/doc.go | 172 ++++++++ .../equinox/internal/go-update/hide_noop.go | 7 + .../internal/go-update/hide_windows.go | 19 + .../go-update/internal/binarydist/License | 22 + .../go-update/internal/binarydist/Readme.md | 7 + .../go-update/internal/binarydist/bzip2.go | 40 ++ .../go-update/internal/binarydist/diff.go | 408 ++++++++++++++++++ .../go-update/internal/binarydist/doc.go | 24 ++ .../go-update/internal/binarydist/encoding.go | 53 +++ .../go-update/internal/binarydist/patch.go | 109 +++++ .../go-update/internal/binarydist/seek.go | 43 ++ .../internal/go-update/internal/osext/LICENSE | 27 ++ .../go-update/internal/osext/README.md | 16 + .../go-update/internal/osext/osext.go | 27 ++ .../go-update/internal/osext/osext_plan9.go | 20 + .../go-update/internal/osext/osext_procfs.go | 36 ++ .../go-update/internal/osext/osext_sysctl.go | 79 ++++ .../go-update/internal/osext/osext_windows.go | 34 ++ .../equinox/internal/go-update/patcher.go | 24 ++ .../equinox/internal/go-update/verifier.go | 74 ++++ .../equinox-io/equinox/internal/osext/LICENSE | 27 ++ .../equinox/internal/osext/README.md | 16 + .../equinox/internal/osext/osext.go | 27 ++ .../equinox/internal/osext/osext_plan9.go | 20 + .../equinox/internal/osext/osext_procfs.go | 36 ++ .../equinox/internal/osext/osext_sysctl.go | 79 ++++ .../equinox/internal/osext/osext_windows.go | 34 ++ .../equinox-io/equinox/proto/proto.go | 42 ++ vendor/github.com/equinox-io/equinox/sdk.go | 330 ++++++++++++++ .../github.com/equinox-io/equinox/sdk_ctx.go | 29 ++ vendor/modules.txt | 8 + 44 files changed, 2584 insertions(+) create mode 100644 cmd/update/update.go create mode 100755 release create mode 100644 vendor/github.com/equinox-io/equinox/.travis.yml create mode 100644 vendor/github.com/equinox-io/equinox/LICENSE create mode 100644 vendor/github.com/equinox-io/equinox/README.md create mode 100644 vendor/github.com/equinox-io/equinox/doc.go create mode 100644 vendor/github.com/equinox-io/equinox/go.mod create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/LICENSE create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/README.md create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/apply.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/doc.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/hide_noop.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/hide_windows.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/Readme.md create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/patcher.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/go-update/verifier.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/LICENSE create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/README.md create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/osext.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/osext_plan9.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/osext_procfs.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/osext_sysctl.go create mode 100644 vendor/github.com/equinox-io/equinox/internal/osext/osext_windows.go create mode 100644 vendor/github.com/equinox-io/equinox/proto/proto.go create mode 100644 vendor/github.com/equinox-io/equinox/sdk.go create mode 100644 vendor/github.com/equinox-io/equinox/sdk_ctx.go diff --git a/cmd/root.go b/cmd/root.go index 67d3df7..e144a71 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "go.transparencylog.net/tl/cmd/cat" "go.transparencylog.net/tl/cmd/get" + "go.transparencylog.net/tl/cmd/update" "go.transparencylog.net/tl/cmd/verify" "go.transparencylog.net/tl/cmd/version" ) @@ -41,6 +42,7 @@ func init() { rootCmd.AddCommand(verify.VerifyCmd) rootCmd.AddCommand(cat.CatCmd) rootCmd.AddCommand(version.Cmd) + rootCmd.AddCommand(update.Cmd) } func Execute() { diff --git a/cmd/update/update.go b/cmd/update/update.go new file mode 100644 index 0000000..00bcd3c --- /dev/null +++ b/cmd/update/update.go @@ -0,0 +1,57 @@ +package update + +import ( + "fmt" + "log" + + "github.com/equinox-io/equinox" + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "update", + Short: "Update to the latest release", + + Args: cobra.ExactArgs(0), + + Run: update, +} + +// assigned when creating a new application in the dashboard +const appID = "app_gCwJN1DfmYU" + +// public portion of signing key generated by `equinox genkey` +var publicKey = []byte(` +-----BEGIN ECDSA PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEHk9AJl154cKxZRECl5oO7GaAAM4Rc5e4 +rBytsXGhOWeZdv/QGXWO3gPq134sTFeOwNwu6GJdk1QhAw85YQc/RKFf2vm2Jut4 +Ad3Me5ph2iwXmgG3tPvBNthqeQgIu58f +-----END ECDSA PUBLIC KEY----- +`) + +func update(cmd *cobra.Command, args []string) { + var opts equinox.Options + if err := opts.SetPublicKeyPEM(publicKey); err != nil { + log.Fatalf("%v", err) + } + + // check for the update + resp, err := equinox.Check(appID, opts) + switch { + case err == equinox.NotAvailableErr: + fmt.Println("No update available, already at the latest version!") + log.Fatalf("%v", err) + case err != nil: + fmt.Println("Update failed:", err) + log.Fatalf("%v", err) + } + + // fetch the update and apply it + err = resp.Apply() + if err != nil { + log.Fatalf("%v", err) + } + + fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion) + return +} diff --git a/go.mod b/go.mod index cd7c772..b06806c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/cavaliercoder/grab v1.0.1-0.20200703095818-d3334b8f122d github.com/dgraph-io/badger/v2 v2.0.3 + github.com/equinox-io/equinox v1.2.0 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index f925f2e..5954fe3 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/equinox-io/equinox v1.2.0 h1:bBS7Ou+Y7Jwgmy8TWSYxEh85WctuFn7FPlgbUzX4DBA= +github.com/equinox-io/equinox v1.2.0/go.mod h1:6s3HJB0PYUNgs0mxmI8fHdfVl3TQ25ieA/PVfr+eyVo= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/release b/release new file mode 100755 index 0000000..9e76af7 --- /dev/null +++ b/release @@ -0,0 +1,7 @@ +equinox release \ + --version=$(git describe --tags) \ + --platforms="darwin_amd64 darwin_arm64 linux_arm linux_arm64 linux_amd64 windows_amd64 windows_arm64" \ + --signing-key=${EQUINOX_KEY} \ + --app="app_gCwJN1DfmYU" \ + --token="${EQUINOX_TOKEN}" \ + go.transparencylog.net/tl diff --git a/vendor/github.com/equinox-io/equinox/.travis.yml b/vendor/github.com/equinox-io/equinox/.travis.yml new file mode 100644 index 0000000..51d4ea5 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - "1.4" + - "1.5" + - "1.7" + - "1.9" + - "1.10" + - "1.11" + - "tip" +matrix: + allow_failures: + - go: tip +script: go test -v github.com/equinox-io/equinox github.com/equinox-io/equinox/proto diff --git a/vendor/github.com/equinox-io/equinox/LICENSE b/vendor/github.com/equinox-io/equinox/LICENSE new file mode 100644 index 0000000..b5763d4 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Equinox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/equinox-io/equinox/README.md b/vendor/github.com/equinox-io/equinox/README.md new file mode 100644 index 0000000..af274ae --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/README.md @@ -0,0 +1,99 @@ +# equinox client SDK [![godoc reference](https://godoc.org/github.com/equinox-io/equinox?status.png)](https://godoc.org/github.com/equinox-io/equinox) + +Package equinox allows applications to remotely update themselves with the [equinox.io](https://equinox.io) service. + +## Minimal Working Example + +```go +import "github.com/equinox-io/equinox" + +const appID = "" + +var publicKey = []byte(` +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx +MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ== +-----END PUBLIC KEY----- +`) + +func update(channel string) error { + opts := equinox.Options{Channel: channel} + if err := opts.SetPublicKeyPEM(publicKey); err != nil { + return err + } + + // check for the update + resp, err := equinox.Check(appID, opts) + switch { + case err == equinox.NotAvailableErr: + fmt.Println("No update available, already at the latest version!") + return nil + case err != nil: + return err + } + + // fetch the update and apply it + err = resp.Apply() + if err != nil { + return err + } + + fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion) + return nil +} +``` + + +## Update To Specific Version + +When you specify a channel in the update options, equinox will try to update the application +to the latest release of your application published to that channel. Instead, you may wish to +update the application to a specific (possibly older) version. You can do this by explicitly setting +Version in the Options struct: + +```go +opts := equinox.Options{Version: "0.1.2"} +``` + +## Prompt For Update + +You may wish to ask the user for approval before updating to a new version. This is as simple +as calling the Check function and only calling Apply on the returned result if the user approves. +Example: + +```go +// check for the update +resp, err := equinox.Check(appID, opts) +switch { +case err == equinox.NotAvailableErr: + fmt.Println("No update available, already at the latest version!") + return nil +case err != nil: + return err +} + +fmt.Println("New version available!") +fmt.Println("Version:", resp.ReleaseVersion) +fmt.Println("Name:", resp.ReleaseTitle) +fmt.Println("Details:", resp.ReleaseDescription) + +ok := prompt("Would you like to update?") + +if !ok { + return +} + +err = resp.Apply() +// ... +``` + +## Generating Keys + +All equinox releases must be signed with a private ECDSA key, and all updates verified with the +public key portion. To do that, you'll need to generate a key pair. The equinox release tool can +generate an ecdsa key pair for you easily: + +```shell +equinox genkey +``` + diff --git a/vendor/github.com/equinox-io/equinox/doc.go b/vendor/github.com/equinox-io/equinox/doc.go new file mode 100644 index 0000000..4a3454e --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/doc.go @@ -0,0 +1,92 @@ +/* +Package equinox allows applications to remotely update themselves with the equinox.io service. + +Minimal Working Example + + import "github.com/equinox-io/equinox" + + const appID = "" + + var publicKey = []byte(` + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx + MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ== + -----END PUBLIC KEY----- + `) + + func update(channel string) error { + opts := equinox.Options{Channel: channel} + if err := opts.SetPublicKeyPEM(publicKey); err != nil { + return err + } + + // check for the update + resp, err := equinox.Check(appID, opts) + switch { + case err == equinox.NotAvailableErr: + fmt.Println("No update available, already at the latest version!") + return nil + case err != nil: + return err + } + + // fetch the update and apply it + err = resp.Apply() + if err != nil { + return err + } + + fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion) + return nil + } + + +Update To Specific Version + +When you specify a channel in the update options, equinox will try to update the application +to the latest release of your application published to that channel. Instead, you may wish to +update the application to a specific (possibly older) version. You can do this by explicitly setting +Version in the Options struct: + + opts := equinox.Options{Version: "0.1.2"} + +Prompt For Update + +You may wish to ask the user for approval before updating to a new version. This is as simple +as calling the Check function and only calling Apply on the returned result if the user approves. +Example: + + // check for the update + resp, err := equinox.Check(appID, opts) + switch { + case err == equinox.NotAvailableErr: + fmt.Println("No update available, already at the latest version!") + return nil + case err != nil: + return err + } + + fmt.Println("New version available!") + fmt.Println("Version:", resp.ReleaseVersion) + fmt.Println("Name:", resp.ReleaseTitle) + fmt.Println("Details:", resp.ReleaseDescription) + + ok := prompt("Would you like to update?") + + if !ok { + return + } + + err = resp.Apply() + // ... + +Generating Keys + +All equinox releases must be signed with a private ECDSA key, and all updates verified with the +public key portion. To do that, you'll need to generate a key pair. The equinox release tool can +generate an ecdsa key pair for you easily: + + equinox genkey + +*/ +package equinox diff --git a/vendor/github.com/equinox-io/equinox/go.mod b/vendor/github.com/equinox-io/equinox/go.mod new file mode 100644 index 0000000..94da5c1 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/go.mod @@ -0,0 +1 @@ +module github.com/equinox-io/equinox diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/LICENSE b/vendor/github.com/equinox-io/equinox/internal/go-update/LICENSE new file mode 100644 index 0000000..418a5d1 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/LICENSE @@ -0,0 +1,13 @@ +Copyright 2015 Alan Shreve + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/README.md b/vendor/github.com/equinox-io/equinox/internal/go-update/README.md new file mode 100644 index 0000000..438ffd4 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/README.md @@ -0,0 +1,65 @@ +# go-update: Build self-updating Go programs [![godoc reference](https://godoc.org/github.com/inconshreveable/go-update?status.png)](https://godoc.org/github.com/inconshreveable/go-update) + +Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets) +A program can update itself by replacing its executable file with a new version. + +It provides the flexibility to implement different updating user experiences +like auto-updating, or manual user-initiated updates. It also boasts +advanced features like binary patching and code signing verification. + +Example of updating from a URL: + +```go +import ( + "fmt" + "net/http" + + "github.com/inconshreveable/go-update" +) + +func doUpdate(url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + err := update.Apply(resp.Body, update.Options{}) + if err != nil { + // error handling + } + return err +} +``` + +## Features + +- Cross platform support (Windows too!) +- Binary patch application +- Checksum verification +- Code signing verification +- Support for updating arbitrary files + +## [equinox.io](https://equinox.io) +[equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides: + +- Hosted updates +- Update channels (stable, beta, nightly, ...) +- Dynamically computed binary diffs +- Automatic key generation and code +- Release tooling with proper code signing +- Update/download metrics + +## API Compatibility Promises +The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor +your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor). + +The `go-update` package makes the following promises about API compatibility: +1. A list of all API-breaking changes will be documented in this README. +1. `go-update` will strive for as few API-breaking changes as possible. + +## API Breaking Changes +- **Sept 3, 2015**: The `Options` struct passed to `Apply` was changed to be passed by value instead of passed by pointer. Old API at `28de026`. +- **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`. + +## License +Apache diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/apply.go b/vendor/github.com/equinox-io/equinox/internal/go-update/apply.go new file mode 100644 index 0000000..e81ff60 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/apply.go @@ -0,0 +1,322 @@ +package update + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/equinox-io/equinox/internal/go-update/internal/osext" +) + +var ( + openFile = os.OpenFile +) + +// Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader. +// +// Apply performs the following actions to ensure a safe cross-platform update: +// +// 1. If configured, applies the contents of the update io.Reader as a binary patch. +// +// 2. If configured, computes the checksum of the new executable and verifies it matches. +// +// 3. If configured, verifies the signature with a public key. +// +// 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file +// +// 5. Renames /path/to/target to /path/to/.target.old +// +// 6. Renames /path/to/.target.new to /path/to/target +// +// 7. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, +// the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. +// +// 8. If the final rename fails, attempts to roll back by renaming /path/to/.target.old +// back to /path/to/target. +// +// If the roll back operation fails, the file system is left in an inconsistent state (betweet steps 5 and 6) where +// there is no new executable file and the old executable file could not be be moved to its original location. In this +// case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether +// the rollback failed by calling RollbackError, see the documentation on that function for additional detail. +func Apply(update io.Reader, opts Options) error { + // validate + verify := false + switch { + case opts.Signature != nil && opts.PublicKey != nil: + // okay + verify = true + case opts.Signature != nil: + return errors.New("no public key to verify signature with") + case opts.PublicKey != nil: + return errors.New("No signature to verify with") + } + + // set defaults + if opts.Hash == 0 { + opts.Hash = crypto.SHA256 + } + if opts.Verifier == nil { + opts.Verifier = NewECDSAVerifier() + } + if opts.TargetMode == 0 { + opts.TargetMode = 0755 + } + + // get target path + var err error + opts.TargetPath, err = opts.getPath() + if err != nil { + return err + } + + var newBytes []byte + if opts.Patcher != nil { + if newBytes, err = opts.applyPatch(update); err != nil { + return err + } + } else { + // no patch to apply, go on through + if newBytes, err = ioutil.ReadAll(update); err != nil { + return err + } + } + + // verify checksum if requested + if opts.Checksum != nil { + if err = opts.verifyChecksum(newBytes); err != nil { + return err + } + } + + if verify { + if err = opts.verifySignature(newBytes); err != nil { + return err + } + } + + // get the directory the executable exists in + updateDir := filepath.Dir(opts.TargetPath) + filename := filepath.Base(opts.TargetPath) + + // Copy the contents of newbinary to a new executable file + newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) + fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.TargetMode) + if err != nil { + return err + } + defer fp.Close() + + _, err = io.Copy(fp, bytes.NewReader(newBytes)) + if err != nil { + return err + } + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + fp.Close() + + // this is where we'll move the executable to so that we can swap in the updated replacement + oldPath := opts.OldSavePath + removeOld := opts.OldSavePath == "" + if removeOld { + oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) + } + + // delete any existing old exec file - this is necessary on Windows for two reasons: + // 1. after a successful update, Windows can't remove the .old file because the process is still running + // 2. windows rename operations fail if the destination file already exists + _ = os.Remove(oldPath) + + // move the existing executable to a new file in the same directory + err = os.Rename(opts.TargetPath, oldPath) + if err != nil { + return err + } + + // move the new exectuable in to become the new program + err = os.Rename(newPath, opts.TargetPath) + + if err != nil { + // move unsuccessful + // + // The filesystem is now in a bad state. We have successfully + // moved the existing binary to a new location, but we couldn't move the new + // binary to take its place. That means there is no file where the current executable binary + // used to be! + // Try to rollback by restoring the old binary to its original path. + rerr := os.Rename(oldPath, opts.TargetPath) + if rerr != nil { + return &rollbackErr{err, rerr} + } + + return err + } + + // move successful, remove the old binary if needed + if removeOld { + errRemove := os.Remove(oldPath) + + // windows has trouble with removing old binaries, so hide it instead + if errRemove != nil { + _ = hideFile(oldPath) + } + } + + return nil +} + +// RollbackError takes an error value returned by Apply and returns the error, if any, +// that occurred when attempting to roll back from a failed update. Applications should +// always call this function on any non-nil errors returned by Apply. +// +// If no rollback was needed or if the rollback was successful, RollbackError returns nil, +// otherwise it returns the error encountered when trying to roll back. +func RollbackError(err error) error { + if err == nil { + return nil + } + if rerr, ok := err.(*rollbackErr); ok { + return rerr.rollbackErr + } + return nil +} + +type rollbackErr struct { + error // original error + rollbackErr error // error encountered while rolling back +} + +type Options struct { + // TargetPath defines the path to the file to update. + // The emptry string means 'the executable file of the running program'. + TargetPath string + + // Create TargetPath replacement with this file mode. If zero, defaults to 0755. + TargetMode os.FileMode + + // Checksum of the new binary to verify against. If nil, no checksum or signature verification is done. + Checksum []byte + + // Public key to use for signature verification. If nil, no signature verification is done. + PublicKey crypto.PublicKey + + // Signature to verify the updated file. If nil, no signature verification is done. + Signature []byte + + // Pluggable signature verification algorithm. If nil, ECDSA is used. + Verifier Verifier + + // Use this hash function to generate the checksum. If not set, SHA256 is used. + Hash crypto.Hash + + // If nil, treat the update as a complete replacement for the contents of the file at TargetPath. + // If non-nil, treat the update contents as a patch and use this object to apply the patch. + Patcher Patcher + + // Store the old executable file at this path after a successful update. + // The empty string means the old executable file will be removed after the update. + OldSavePath string +} + +// CheckPermissions determines whether the process has the correct permissions to +// perform the requested update. If the update can proceed, it returns nil, otherwise +// it returns the error that would occur if an update were attempted. +func (o *Options) CheckPermissions() error { + // get the directory the file exists in + path, err := o.getPath() + if err != nil { + return err + } + + fileDir := filepath.Dir(path) + fileName := filepath.Base(path) + + // attempt to open a file in the file's directory + newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName)) + fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.TargetMode) + if err != nil { + return err + } + fp.Close() + + _ = os.Remove(newPath) + return nil +} + +// SetPublicKeyPEM is a convenience method to set the PublicKey property +// used for checking a completed update's signature by parsing a +// Public Key formatted as PEM data. +func (o *Options) SetPublicKeyPEM(pembytes []byte) error { + block, _ := pem.Decode(pembytes) + if block == nil { + return errors.New("couldn't parse PEM data") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + o.PublicKey = pub + return nil +} + +func (o *Options) getPath() (string, error) { + if o.TargetPath == "" { + return osext.Executable() + } else { + return o.TargetPath, nil + } +} + +func (o *Options) applyPatch(patch io.Reader) ([]byte, error) { + // open the file to patch + old, err := os.Open(o.TargetPath) + if err != nil { + return nil, err + } + defer old.Close() + + // apply the patch + var applied bytes.Buffer + if err = o.Patcher.Patch(old, &applied, patch); err != nil { + return nil, err + } + + return applied.Bytes(), nil +} + +func (o *Options) verifyChecksum(updated []byte) error { + checksum, err := checksumFor(o.Hash, updated) + if err != nil { + return err + } + + if !bytes.Equal(o.Checksum, checksum) { + return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum) + } + return nil +} + +func (o *Options) verifySignature(updated []byte) error { + checksum, err := checksumFor(o.Hash, updated) + if err != nil { + return err + } + return o.Verifier.VerifySignature(checksum, o.Signature, o.Hash, o.PublicKey) +} + +func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) { + if !h.Available() { + return nil, errors.New("requested hash function not available") + } + hash := h.New() + hash.Write(payload) // guaranteed not to error + return hash.Sum([]byte{}), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/doc.go b/vendor/github.com/equinox-io/equinox/internal/go-update/doc.go new file mode 100644 index 0000000..468411f --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/doc.go @@ -0,0 +1,172 @@ +/* +Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets). + +For complete updating solutions please see Equinox (https://equinox.io) and go-tuf (https://github.com/flynn/go-tuf). + +Basic Example + +This example shows how to update a program remotely from a URL. + + import ( + "fmt" + "net/http" + + "github.com/inconshreveable/go-update" + ) + + func doUpdate(url string) error { + // request the new file + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + err := update.Apply(resp.Body, update.Options{}) + if err != nil { + if rerr := update.RollbackError(err); rerr != nil { + fmt.Println("Failed to rollback from bad update: %v", rerr) + } + } + return err + } + + +Binary Patching + +Go binaries can often be large. It can be advantageous to only ship a binary patch to a client +instead of the complete program text of a new version. + +This example shows how to update a program with a bsdiff binary patch. Other patch formats +may be applied by implementing the Patcher interface. + + import ( + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + func updateWithPatch(patch io.Reader) error { + err := update.Apply(patch, update.Options{ + Patcher: update.NewBSDiffPatcher() + }) + if err != nil { + // error handling + } + return err + } + +Checksum Verification + +Updating executable code on a computer can be a dangerous operation unless you +take the appropriate steps to guarantee the authenticity of the new code. While +checksum verification is important, it should always be combined with signature +verification (next section) to guarantee that the code came from a trusted party. + +go-update validates SHA256 checksums by default, but this is pluggable via the Hash +property on the Options struct. + +This example shows how to guarantee that the newly-updated binary is verified to +have an appropriate checksum (that was otherwise retrived via a secure channel) +specified as a hex string. + + import ( + "crypto" + _ "crypto/sha256" + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + func updateWithChecksum(binary io.Reader, hexChecksum string) error { + checksum, err := hex.DecodeString(hexChecksum) + if err != nil { + return err + } + err = update.Apply(binary, update.Options{ + Hash: crypto.SHA256, // this is the default, you don't need to specify it + Checksum: checksum, + }) + if err != nil { + // error handling + } + return err + } + +Cryptographic Signature Verification + +Cryptographic verification of new code from an update is an extremely important way to guarantee the +security and integrity of your updates. + +Verification is performed by validating the signature of a hash of the new file. This +means nothing changes if you apply your update with a patch. + +This example shows how to add signature verification to your updates. To make all of this work +an application distributor must first create a public/private key pair and embed the public key +into their application. When they issue a new release, the issuer must sign the new executable file +with the private key and distribute the signature along with the update. + + import ( + "crypto" + _ "crypto/sha256" + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + var publicKey = []byte(` + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx + MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ== + -----END PUBLIC KEY----- + `) + + func verifiedUpdate(binary io.Reader, hexChecksum, hexSignature string) { + checksum, err := hex.DecodeString(hexChecksum) + if err != nil { + return err + } + signature, err := hex.DecodeString(hexSignature) + if err != nil { + return err + } + opts := update.Options{ + Checksum: checksum, + Signature: signature, + Hash: crypto.SHA256, // this is the default, you don't need to specify it + Verifier: update.NewECDSAVerifier(), // this is the default, you don't need to specify it + } + err = opts.SetPublicKeyPEM(publicKey) + if err != nil { + return err + } + err = update.Apply(binary, opts) + if err != nil { + // error handling + } + return err + } + + +Building Single-File Go Binaries + +In order to update a Go application with go-update, you must distributed it as a single executable. +This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates). +In order to update applications like these, you'll want to make sure to embed those asset files into +the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata + +Non-Goals + +Mechanisms and protocols for determining whether an update should be applied and, if so, which one are +out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io) +for more complete solutions. + +go-update only works for self-updating applications that are distributed as a single binary, i.e. +applications that do not have additional assets or dependency files. +Updating application that are distributed as mutliple on-disk files is out of scope, although this +may change in future versions of this library. + +*/ +package update diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/hide_noop.go b/vendor/github.com/equinox-io/equinox/internal/go-update/hide_noop.go new file mode 100644 index 0000000..3707756 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/hide_noop.go @@ -0,0 +1,7 @@ +// +build !windows + +package update + +func hideFile(path string) error { + return nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/hide_windows.go b/vendor/github.com/equinox-io/equinox/internal/go-update/hide_windows.go new file mode 100644 index 0000000..c368b9c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/hide_windows.go @@ -0,0 +1,19 @@ +package update + +import ( + "syscall" + "unsafe" +) + +func hideFile(path string) error { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + setFileAttributes := kernel32.NewProc("SetFileAttributesW") + + r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2) + + if r1 == 0 { + return err + } else { + return nil + } +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License new file mode 100644 index 0000000..183c389 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License @@ -0,0 +1,22 @@ +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/Readme.md b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/Readme.md new file mode 100644 index 0000000..dadc368 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/Readme.md @@ -0,0 +1,7 @@ +# binarydist + +Package binarydist implements binary diff and patch as described on +. It reads and writes files +compatible with the tools there. + +Documentation at . diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go new file mode 100644 index 0000000..a2516b8 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go @@ -0,0 +1,40 @@ +package binarydist + +import ( + "io" + "os/exec" +) + +type bzip2Writer struct { + c *exec.Cmd + w io.WriteCloser +} + +func (w bzip2Writer) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w bzip2Writer) Close() error { + if err := w.w.Close(); err != nil { + return err + } + return w.c.Wait() +} + +// Package compress/bzip2 implements only decompression, +// so we'll fake it by running bzip2 in another process. +func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { + var bw bzip2Writer + bw.c = exec.Command("bzip2", "-c") + bw.c.Stdout = w + + if bw.w, err = bw.c.StdinPipe(); err != nil { + return nil, err + } + + if err = bw.c.Start(); err != nil { + return nil, err + } + + return bw, nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go new file mode 100644 index 0000000..1d2d951 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go @@ -0,0 +1,408 @@ +package binarydist + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } + +func split(I, V []int, start, length, h int) { + var i, j, k, x, jj, kk int + + if length < 16 { + for k = start; k < start+length; k += j { + j = 1 + x = V[I[k]+h] + for i = 1; k+i < start+length; i++ { + if V[I[k+i]+h] < x { + x = V[I[k+i]+h] + j = 0 + } + if V[I[k+i]+h] == x { + swap(I, k+i, k+j) + j++ + } + } + for i = 0; i < j; i++ { + V[I[k+i]] = k + j - 1 + } + if j == 1 { + I[k] = -1 + } + } + return + } + + x = V[I[start+length/2]+h] + jj = 0 + kk = 0 + for i = start; i < start+length; i++ { + if V[I[i]+h] < x { + jj++ + } + if V[I[i]+h] == x { + kk++ + } + } + jj += start + kk += jj + + i = start + j = 0 + k = 0 + for i < jj { + if V[I[i]+h] < x { + i++ + } else if V[I[i]+h] == x { + swap(I, i, jj+j) + j++ + } else { + swap(I, i, kk+k) + k++ + } + } + + for jj+j < kk { + if V[I[jj+j]+h] == x { + j++ + } else { + swap(I, jj+j, kk+k) + k++ + } + } + + if jj > start { + split(I, V, start, jj-start, h) + } + + for i = 0; i < kk-jj; i++ { + V[I[jj+i]] = kk - 1 + } + if jj == kk-1 { + I[jj] = -1 + } + + if start+length > kk { + split(I, V, kk, start+length-kk, h) + } +} + +func qsufsort(obuf []byte) []int { + var buckets [256]int + var i, h int + I := make([]int, len(obuf)+1) + V := make([]int, len(obuf)+1) + + for _, c := range obuf { + buckets[c]++ + } + for i = 1; i < 256; i++ { + buckets[i] += buckets[i-1] + } + copy(buckets[1:], buckets[:]) + buckets[0] = 0 + + for i, c := range obuf { + buckets[c]++ + I[buckets[c]] = i + } + + I[0] = len(obuf) + for i, c := range obuf { + V[i] = buckets[c] + } + + V[len(obuf)] = 0 + for i = 1; i < 256; i++ { + if buckets[i] == buckets[i-1]+1 { + I[buckets[i]] = -1 + } + } + I[0] = -1 + + for h = 1; I[0] != -(len(obuf) + 1); h += h { + var n int + for i = 0; i < len(obuf)+1; { + if I[i] < 0 { + n -= I[i] + i -= I[i] + } else { + if n != 0 { + I[i-n] = -n + } + n = V[I[i]] + 1 - i + split(I, V, i, n, h) + i += n + n = 0 + } + } + if n != 0 { + I[i-n] = -n + } + } + + for i = 0; i < len(obuf)+1; i++ { + I[V[i]] = i + } + return I +} + +func matchlen(a, b []byte) (i int) { + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return i +} + +func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { + if en-st < 2 { + x := matchlen(obuf[I[st]:], nbuf) + y := matchlen(obuf[I[en]:], nbuf) + + if x > y { + return I[st], x + } else { + return I[en], y + } + } + + x := st + (en-st)/2 + if bytes.Compare(obuf[I[x]:], nbuf) < 0 { + return search(I, obuf, nbuf, x, en) + } else { + return search(I, obuf, nbuf, st, x) + } + panic("unreached") +} + +// Diff computes the difference between old and new, according to the bsdiff +// algorithm, and writes the result to patch. +func Diff(old, new io.Reader, patch io.Writer) error { + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf, err := ioutil.ReadAll(new) + if err != nil { + return err + } + + pbuf, err := diffBytes(obuf, nbuf) + if err != nil { + return err + } + + _, err = patch.Write(pbuf) + return err +} + +func diffBytes(obuf, nbuf []byte) ([]byte, error) { + var patch seekBuffer + err := diff(obuf, nbuf, &patch) + if err != nil { + return nil, err + } + return patch.buf, nil +} + +func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { + var lenf int + I := qsufsort(obuf) + db := make([]byte, len(nbuf)) + eb := make([]byte, len(nbuf)) + var dblen, eblen int + + var hdr header + hdr.Magic = magic + hdr.NewSize = int64(len(nbuf)) + err := binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + + // Compute the differences, writing ctrl as we go + pfbz2, err := newBzip2Writer(patch) + if err != nil { + return err + } + var scan, pos, length int + var lastscan, lastpos, lastoffset int + for scan < len(nbuf) { + var oldscore int + scan += length + for scsc := scan; scan < len(nbuf); scan++ { + pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) + + for ; scsc < scan+length; scsc++ { + if scsc+lastoffset < len(obuf) && + obuf[scsc+lastoffset] == nbuf[scsc] { + oldscore++ + } + } + + if (length == oldscore && length != 0) || length > oldscore+8 { + break + } + + if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { + oldscore-- + } + } + + if length != oldscore || scan == len(nbuf) { + var s, Sf int + lenf = 0 + for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { + if obuf[lastpos+i] == nbuf[lastscan+i] { + s++ + } + i++ + if s*2-i > Sf*2-lenf { + Sf = s + lenf = i + } + } + + lenb := 0 + if scan < len(nbuf) { + var s, Sb int + for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { + if obuf[pos-i] == nbuf[scan-i] { + s++ + } + if s*2-i > Sb*2-lenb { + Sb = s + lenb = i + } + } + } + + if lastscan+lenf > scan-lenb { + overlap := (lastscan + lenf) - (scan - lenb) + s := 0 + Ss := 0 + lens := 0 + for i := 0; i < overlap; i++ { + if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { + s++ + } + if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { + s-- + } + if s > Ss { + Ss = s + lens = i + 1 + } + } + + lenf += lens - overlap + lenb -= lens + } + + for i := 0; i < lenf; i++ { + db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] + } + for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { + eb[eblen+i] = nbuf[lastscan+lenf+i] + } + + dblen += lenf + eblen += (scan - lenb) - (lastscan + lenf) + + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) + if err != nil { + pfbz2.Close() + return err + } + + val := (scan - lenb) - (lastscan + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + val = (pos - lenb) - (lastpos + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + lastscan = scan - lenb + lastpos = pos - lenb + lastoffset = pos - scan + } + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed ctrl data + l64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.CtrlLen = int64(l64 - 32) + + // Write compressed diff data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err := pfbz2.Write(db[:dblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != dblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed diff data + n64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.DiffLen = n64 - l64 + + // Write compressed extra data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err = pfbz2.Write(eb[:eblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != eblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Seek to the beginning, write the header, and close the file + _, err = patch.Seek(0, 0) + if err != nil { + return err + } + err = binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go new file mode 100644 index 0000000..3c92d87 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go @@ -0,0 +1,24 @@ +// Package binarydist implements binary diff and patch as described on +// http://www.daemonology.net/bsdiff/. It reads and writes files +// compatible with the tools there. +package binarydist + +var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} + +// File format: +// 0 8 "BSDIFF40" +// 8 8 X +// 16 8 Y +// 24 8 sizeof(newfile) +// 32 X bzip2(control block) +// 32+X Y bzip2(diff block) +// 32+X+Y ??? bzip2(extra block) +// with control block a set of triples (x,y,z) meaning "add x bytes +// from oldfile to x bytes from the diff block; copy y bytes from the +// extra block; seek forwards in oldfile by z bytes". +type header struct { + Magic [8]byte + CtrlLen int64 + DiffLen int64 + NewSize int64 +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go new file mode 100644 index 0000000..75ba585 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go @@ -0,0 +1,53 @@ +package binarydist + +// SignMagLittleEndian is the numeric encoding used by the bsdiff tools. +// It implements binary.ByteOrder using a sign-magnitude format +// and little-endian byte order. Only methods Uint64 and String +// have been written; the rest panic. +type signMagLittleEndian struct{} + +func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } + +func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } + +func (signMagLittleEndian) Uint64(b []byte) uint64 { + y := int64(b[0]) | + int64(b[1])<<8 | + int64(b[2])<<16 | + int64(b[3])<<24 | + int64(b[4])<<32 | + int64(b[5])<<40 | + int64(b[6])<<48 | + int64(b[7]&0x7f)<<56 + + if b[7]&0x80 != 0 { + y = -y + } + return uint64(y) +} + +func (signMagLittleEndian) PutUint64(b []byte, v uint64) { + x := int64(v) + neg := x < 0 + if neg { + x = -x + } + + b[0] = byte(x) + b[1] = byte(x >> 8) + b[2] = byte(x >> 16) + b[3] = byte(x >> 24) + b[4] = byte(x >> 32) + b[5] = byte(x >> 40) + b[6] = byte(x >> 48) + b[7] = byte(x >> 56) + if neg { + b[7] |= 0x80 + } +} + +func (signMagLittleEndian) String() string { return "signMagLittleEndian" } diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go new file mode 100644 index 0000000..eb03225 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go @@ -0,0 +1,109 @@ +package binarydist + +import ( + "bytes" + "compress/bzip2" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +var ErrCorrupt = errors.New("corrupt patch") + +// Patch applies patch to old, according to the bspatch algorithm, +// and writes the result to new. +func Patch(old io.Reader, new io.Writer, patch io.Reader) error { + var hdr header + err := binary.Read(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + if hdr.Magic != magic { + return ErrCorrupt + } + if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { + return ErrCorrupt + } + + ctrlbuf := make([]byte, hdr.CtrlLen) + _, err = io.ReadFull(patch, ctrlbuf) + if err != nil { + return err + } + cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) + + diffbuf := make([]byte, hdr.DiffLen) + _, err = io.ReadFull(patch, diffbuf) + if err != nil { + return err + } + dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) + + // The entire rest of the file is the extra block. + epfbz2 := bzip2.NewReader(patch) + + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf := make([]byte, hdr.NewSize) + + var oldpos, newpos int64 + for newpos < hdr.NewSize { + var ctrl struct{ Add, Copy, Seek int64 } + err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) + if err != nil { + return err + } + + // Sanity-check + if newpos+ctrl.Add > hdr.NewSize { + return ErrCorrupt + } + + // Read diff string + _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) + if err != nil { + return ErrCorrupt + } + + // Add old data to diff string + for i := int64(0); i < ctrl.Add; i++ { + if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { + nbuf[newpos+i] += obuf[oldpos+i] + } + } + + // Adjust pointers + newpos += ctrl.Add + oldpos += ctrl.Add + + // Sanity-check + if newpos+ctrl.Copy > hdr.NewSize { + return ErrCorrupt + } + + // Read extra string + _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) + if err != nil { + return ErrCorrupt + } + + // Adjust pointers + newpos += ctrl.Copy + oldpos += ctrl.Seek + } + + // Write the new file + for len(nbuf) > 0 { + n, err := new.Write(nbuf) + if err != nil { + return err + } + nbuf = nbuf[n:] + } + + return nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go new file mode 100644 index 0000000..96c0346 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go @@ -0,0 +1,43 @@ +package binarydist + +import ( + "errors" +) + +type seekBuffer struct { + buf []byte + pos int +} + +func (b *seekBuffer) Write(p []byte) (n int, err error) { + n = copy(b.buf[b.pos:], p) + if n == len(p) { + b.pos += n + return n, nil + } + b.buf = append(b.buf, p[n:]...) + b.pos += len(p) + return len(p), nil +} + +func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { + var abs int64 + switch whence { + case 0: + abs = offset + case 1: + abs = int64(b.pos) + offset + case 2: + abs = int64(len(b.buf)) + offset + default: + return 0, errors.New("binarydist: invalid whence") + } + if abs < 0 { + return 0, errors.New("binarydist: negative position") + } + if abs >= 1<<31 { + return 0, errors.New("binarydist: position out of range") + } + b.pos = int(abs) + return abs, nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md new file mode 100644 index 0000000..61350ba --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md @@ -0,0 +1,16 @@ +### Extensions to the "os" package. + +## Find the current Executable and ExecutableFolder. + +There is sometimes utility in finding the current executable file +that is running. This can be used for upgrading the current executable +or finding resources located relative to the executable file. Both +working directory and the os.Args[0] value are arbitrary and cannot +be relied on; os.Args[0] can be "faked". + +Multi-platform and supports: + * Linux + * OS X + * Windows + * Plan 9 + * BSDs. diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go new file mode 100644 index 0000000..7bef46f --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go @@ -0,0 +1,27 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go new file mode 100644 index 0000000..655750c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go 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 osext + +import ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go new file mode 100644 index 0000000..b2598bc --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd solaris dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd", "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go new file mode 100644 index 0000000..b66cac8 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd + +package osext + +import ( + "os" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + var err error + execPath := string(buf) + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go 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 osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/patcher.go b/vendor/github.com/equinox-io/equinox/internal/go-update/patcher.go new file mode 100644 index 0000000..8b36754 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/patcher.go @@ -0,0 +1,24 @@ +package update + +import ( + "io" + + "github.com/equinox-io/equinox/internal/go-update/internal/binarydist" +) + +// Patcher defines an interface for applying binary patches to an old item to get an updated item. +type Patcher interface { + Patch(old io.Reader, new io.Writer, patch io.Reader) error +} + +type patchFn func(io.Reader, io.Writer, io.Reader) error + +func (fn patchFn) Patch(old io.Reader, new io.Writer, patch io.Reader) error { + return fn(old, new, patch) +} + +// NewBSDifferPatcher returns a new Patcher that applies binary patches using +// the bsdiff algorithm. See http://www.daemonology.net/bsdiff/ +func NewBSDiffPatcher() Patcher { + return patchFn(binarydist.Patch) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/go-update/verifier.go b/vendor/github.com/equinox-io/equinox/internal/go-update/verifier.go new file mode 100644 index 0000000..af1fc57 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/go-update/verifier.go @@ -0,0 +1,74 @@ +package update + +import ( + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "encoding/asn1" + "errors" + "math/big" +) + +// Verifier defines an interface for verfiying an update's signature with a public key. +type Verifier interface { + VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error +} + +type verifyFn func([]byte, []byte, crypto.Hash, crypto.PublicKey) error + +func (fn verifyFn) VerifySignature(checksum []byte, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + return fn(checksum, signature, hash, publicKey) +} + +// NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates. +func NewRSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*rsa.PublicKey) + if !ok { + return errors.New("not a valid RSA public key") + } + return rsa.VerifyPKCS1v15(key, hash, checksum, signature) + }) +} + +type rsDER struct { + R *big.Int + S *big.Int +} + +// NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates. +func NewECDSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return errors.New("not a valid ECDSA public key") + } + var rs rsDER + if _, err := asn1.Unmarshal(signature, &rs); err != nil { + return err + } + if !ecdsa.Verify(key, checksum, rs.R, rs.S) { + return errors.New("failed to verify ecsda signature") + } + return nil + }) +} + +// NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates. +func NewDSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*dsa.PublicKey) + if !ok { + return errors.New("not a valid DSA public key") + } + var rs rsDER + if _, err := asn1.Unmarshal(signature, &rs); err != nil { + return err + } + if !dsa.Verify(key, checksum, rs.R, rs.S) { + return errors.New("failed to verify ecsda signature") + } + return nil + }) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/LICENSE b/vendor/github.com/equinox-io/equinox/internal/osext/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/README.md b/vendor/github.com/equinox-io/equinox/internal/osext/README.md new file mode 100644 index 0000000..61350ba --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/README.md @@ -0,0 +1,16 @@ +### Extensions to the "os" package. + +## Find the current Executable and ExecutableFolder. + +There is sometimes utility in finding the current executable file +that is running. This can be used for upgrading the current executable +or finding resources located relative to the executable file. Both +working directory and the os.Args[0] value are arbitrary and cannot +be relied on; os.Args[0] can be "faked". + +Multi-platform and supports: + * Linux + * OS X + * Windows + * Plan 9 + * BSDs. diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/osext.go b/vendor/github.com/equinox-io/equinox/internal/osext/osext.go new file mode 100644 index 0000000..7bef46f --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/osext.go @@ -0,0 +1,27 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/osext_plan9.go b/vendor/github.com/equinox-io/equinox/internal/osext/osext_plan9.go new file mode 100644 index 0000000..655750c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go 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 osext + +import ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/osext_procfs.go b/vendor/github.com/equinox-io/equinox/internal/osext/osext_procfs.go new file mode 100644 index 0000000..b2598bc --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd solaris dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd", "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/osext_sysctl.go b/vendor/github.com/equinox-io/equinox/internal/osext/osext_sysctl.go new file mode 100644 index 0000000..b66cac8 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/osext_sysctl.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd + +package osext + +import ( + "os" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + var err error + execPath := string(buf) + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/equinox-io/equinox/internal/osext/osext_windows.go b/vendor/github.com/equinox-io/equinox/internal/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/internal/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go 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 osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/vendor/github.com/equinox-io/equinox/proto/proto.go b/vendor/github.com/equinox-io/equinox/proto/proto.go new file mode 100644 index 0000000..f598b01 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/proto/proto.go @@ -0,0 +1,42 @@ +/* +package proto defines a set of structures used to negotiate an update between an +an application (the client) and an Equinox update service. +*/ +package proto + +import "time" + +type PatchKind string + +const ( + PatchNone PatchKind = "none" + PatchBSDiff PatchKind = "bsdiff" +) + +type Request struct { + AppID string `json:"app_id"` + Channel string `json:"channel"` + OS string `json:"os"` + Arch string `json:"arch"` + GoARM string `json:"goarm"` + TargetVersion string `json:"target_version"` + + CurrentVersion string `json:"current_version"` + CurrentSHA256 string `json:"current_sha256"` +} + +type Response struct { + Available bool `json:"available"` + DownloadURL string `json:"download_url"` + Checksum string `json:"checksum"` + Signature string `json:"signature"` + Patch PatchKind `json:"patch_type"` + Release Release `json:"release"` +} + +type Release struct { + Title string `json:"title"` + Version string `json:"version"` + Description string `json:"description"` + CreateDate time.Time `json:"create_date"` +} diff --git a/vendor/github.com/equinox-io/equinox/sdk.go b/vendor/github.com/equinox-io/equinox/sdk.go new file mode 100644 index 0000000..7300c5c --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/sdk.go @@ -0,0 +1,330 @@ +package equinox + +import ( + "bytes" + "crypto" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "runtime" + "time" + + "github.com/equinox-io/equinox/internal/go-update" + "github.com/equinox-io/equinox/internal/osext" + "github.com/equinox-io/equinox/proto" +) + +const protocolVersion = "1" +const defaultCheckURL = "https://update.equinox.io/check" +const userAgent = "EquinoxSDK/1.0" + +var NotAvailableErr = errors.New("No update available") + +type Options struct { + // Channel specifies the name of an Equinox release channel to check for + // a newer version of the application. + // + // If empty, defaults to 'stable'. + Channel string + + // Version requests an update to a specific version of the application. + // If specified, `Channel` is ignored. + Version string + + // TargetPath defines the path to the file to update. + // The emptry string means 'the executable file of the running program'. + TargetPath string + + // Create TargetPath replacement with this file mode. If zero, defaults to 0755. + TargetMode os.FileMode + + // Public key to use for signature verification. If nil, no signature + // verification is done. Use `SetPublicKeyPEM` to set this field with PEM data. + PublicKey crypto.PublicKey + + // Target operating system of the update. Uses the same standard OS names used + // by Go build tags (windows, darwin, linux, etc). + // If empty, it will be populated by consulting runtime.GOOS + OS string + + // Target architecture of the update. Uses the same standard Arch names used + // by Go build tags (amd64, 386, arm, etc). + // If empty, it will be populated by consulting runtime.GOARCH + Arch string + + // Target ARM architecture, if a specific one if required. Uses the same names + // as the GOARM environment variable (5, 6, 7). + // + // GoARM is ignored if Arch != 'arm'. + // GoARM is ignored if it is the empty string. Omit it if you do not need + // to distinguish between ARM versions. + GoARM string + + // The current application version. This is used for statistics and reporting only, + // it is optional. + CurrentVersion string + + // CheckURL is the URL to request an update check from. You should only set + // this if you are running an on-prem Equinox server. + // If empty the default Equinox update service endpoint is used. + CheckURL string + + // HTTPClient is used to make all HTTP requests necessary for the update check protocol. + // You may configure it to use custom timeouts, proxy servers or other behaviors. + HTTPClient *http.Client +} + +// Response is returned by Check when an update is available. It may be +// passed to Apply to perform the update. +type Response struct { + // Version of the release that will be updated to if applied. + ReleaseVersion string + + // Title of the the release + ReleaseTitle string + + // Additional details about the release + ReleaseDescription string + + // Creation date of the release + ReleaseDate time.Time + + downloadURL string + checksum []byte + signature []byte + patch proto.PatchKind + opts Options +} + +// SetPublicKeyPEM is a convenience method to set the PublicKey property +// used for checking a completed update's signature by parsing a +// Public Key formatted as PEM data. +func (o *Options) SetPublicKeyPEM(pembytes []byte) error { + block, _ := pem.Decode(pembytes) + if block == nil { + return errors.New("couldn't parse PEM data") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + o.PublicKey = pub + return nil +} + +// Check communicates with an Equinox update service to determine if +// an update for the given application matching the specified options is +// available. The returned error is nil only if an update is available. +// +// The appID is issued to you when creating an application at https://equinox.io +// +// You can compare the returned error to NotAvailableErr to differentiate between +// a successful check that found no update from other errors like a failed +// network connection. +func Check(appID string, opts Options) (Response, error) { + var req, err = checkRequest(appID, &opts) + + if err != nil { + return Response{}, err + } + + return doCheckRequest(opts, req) +} + +func checkRequest(appID string, opts *Options) (*http.Request, error) { + if opts.Channel == "" { + opts.Channel = "stable" + } + if opts.TargetPath == "" { + var err error + opts.TargetPath, err = osext.Executable() + if err != nil { + return nil, err + } + } + if opts.OS == "" { + opts.OS = runtime.GOOS + } + if opts.Arch == "" { + opts.Arch = runtime.GOARCH + } + if opts.CheckURL == "" { + opts.CheckURL = defaultCheckURL + } + if opts.HTTPClient == nil { + opts.HTTPClient = new(http.Client) + } + opts.HTTPClient.Transport = newUserAgentTransport(userAgent, opts.HTTPClient.Transport) + + checksum := computeChecksum(opts.TargetPath) + + payload, err := json.Marshal(proto.Request{ + AppID: appID, + Channel: opts.Channel, + OS: opts.OS, + Arch: opts.Arch, + GoARM: opts.GoARM, + TargetVersion: opts.Version, + CurrentVersion: opts.CurrentVersion, + CurrentSHA256: checksum, + }) + + req, err := http.NewRequest("POST", opts.CheckURL, bytes.NewReader(payload)) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", fmt.Sprintf("application/json; q=1; version=%s; charset=utf-8", protocolVersion)) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Close = true + + return req, err +} + +func doCheckRequest(opts Options, req *http.Request) (r Response, err error) { + resp, err := opts.HTTPClient.Do(req) + if err != nil { + return r, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, _ := ioutil.ReadAll(resp.Body) + return r, fmt.Errorf("Server responded with %s: %s", resp.Status, body) + } + + var protoResp proto.Response + err = json.NewDecoder(resp.Body).Decode(&protoResp) + if err != nil { + return r, err + } + + if !protoResp.Available { + return r, NotAvailableErr + } + + r.ReleaseVersion = protoResp.Release.Version + r.ReleaseTitle = protoResp.Release.Title + r.ReleaseDescription = protoResp.Release.Description + r.ReleaseDate = protoResp.Release.CreateDate + r.downloadURL = protoResp.DownloadURL + r.patch = protoResp.Patch + r.opts = opts + r.checksum, err = hex.DecodeString(protoResp.Checksum) + if err != nil { + return r, err + } + r.signature, err = hex.DecodeString(protoResp.Signature) + if err != nil { + return r, err + } + + return r, nil +} + +func computeChecksum(path string) string { + f, err := os.Open(path) + if err != nil { + return "" + } + defer f.Close() + h := sha256.New() + _, err = io.Copy(h, f) + if err != nil { + return "" + } + return hex.EncodeToString(h.Sum(nil)) +} + +// Apply performs an update of the current executable (or TargetFile, if it was +// set on the Options) with the update specified by Response. +// +// Error is nil if and only if the entire update completes successfully. +func (r Response) Apply() error { + var req, opts, err = r.applyRequest() + + if err != nil { + return err + } + + return r.applyUpdate(req, opts) +} + +func (r Response) applyRequest() (*http.Request, update.Options, error) { + opts := update.Options{ + TargetPath: r.opts.TargetPath, + TargetMode: r.opts.TargetMode, + Checksum: r.checksum, + Signature: r.signature, + Verifier: update.NewECDSAVerifier(), + PublicKey: r.opts.PublicKey, + } + switch r.patch { + case proto.PatchBSDiff: + opts.Patcher = update.NewBSDiffPatcher() + } + + if err := opts.CheckPermissions(); err != nil { + return nil, opts, err + } + + req, err := http.NewRequest("GET", r.downloadURL, nil) + return req, opts, err +} + +func (r Response) applyUpdate(req *http.Request, opts update.Options) error { + // fetch the update + req.Close = true + resp, err := r.opts.HTTPClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + // check that we got a patch + if resp.StatusCode >= 400 { + msg := "error downloading patch" + + id := resp.Header.Get("Request-Id") + if id != "" { + msg += ", request " + id + } + + blob, err := ioutil.ReadAll(resp.Body) + if err == nil { + msg += ": " + string(bytes.TrimSpace(blob)) + } + return fmt.Errorf(msg) + } + + return update.Apply(resp.Body, opts) +} + +type userAgentTransport struct { + userAgent string + http.RoundTripper +} + +func newUserAgentTransport(userAgent string, rt http.RoundTripper) *userAgentTransport { + if rt == nil { + rt = http.DefaultTransport + } + return &userAgentTransport{userAgent, rt} +} + +func (t *userAgentTransport) RoundTrip(r *http.Request) (*http.Response, error) { + if r.Header.Get("User-Agent") == "" { + r.Header.Set("User-Agent", t.userAgent) + } + return t.RoundTripper.RoundTrip(r) +} diff --git a/vendor/github.com/equinox-io/equinox/sdk_ctx.go b/vendor/github.com/equinox-io/equinox/sdk_ctx.go new file mode 100644 index 0000000..596c642 --- /dev/null +++ b/vendor/github.com/equinox-io/equinox/sdk_ctx.go @@ -0,0 +1,29 @@ +// +build go1.7 + +package equinox + +import ( + "context" +) + +// CheckContext is like Check but includes a context. +func CheckContext(ctx context.Context, appID string, opts Options) (Response, error) { + var req, err = checkRequest(appID, &opts) + + if err != nil { + return Response{}, err + } + + return doCheckRequest(opts, req.WithContext(ctx)) +} + +// ApplyContext is like Apply but includes a context. +func (r Response) ApplyContext(ctx context.Context) error { + var req, opts, err = r.applyRequest() + + if err != nil { + return err + } + + return r.applyUpdate(req.WithContext(ctx), opts) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0be01cd..ea01d7f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -22,6 +22,14 @@ github.com/dgraph-io/ristretto/z github.com/dgryski/go-farm # github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize +# github.com/equinox-io/equinox v1.2.0 +## explicit +github.com/equinox-io/equinox +github.com/equinox-io/equinox/internal/go-update +github.com/equinox-io/equinox/internal/go-update/internal/binarydist +github.com/equinox-io/equinox/internal/go-update/internal/osext +github.com/equinox-io/equinox/internal/osext +github.com/equinox-io/equinox/proto # github.com/golang/protobuf v1.3.1 github.com/golang/protobuf/proto # github.com/golang/snappy v0.0.1