update

package module
v0.0.0-...-b1614fe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2016 License: Apache-2.0 Imports: 22 Imported by: 1

README

go-update: Build self-updating Go programs godoc reference

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:

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

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 or 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.
  2. 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

Documentation

Overview

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.

go-update allows a program to 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.

Updating your program to a new version is as easy as:

err, errRecover := update.New().FromUrl("http://release.example.com/2.0/myprogram")
if err != nil {
	fmt.Printf("Update failed: %v\n", err)
}

You may also choose to update from other data sources such as a file or an io.Reader:

err, errRecover := update.New().FromFile("/path/to/update")

Binary Diff Patching

Binary diff updates are supported and easy to use:

up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF)
err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")

Checksum Verification

You should also verify the checksum of new updates as well as verify the digital signature of an update. Note that even when you choose to apply a patch, the checksum is verified against the complete update after that patch has been applied.

up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")

Updating other files

Updating arbitrary files is also supported. You may update files which are not the currently running program:

up := update.New().Target("/usr/local/bin/some-program")
err, errRecover := up.FromUrl("http://release.example.com/2.0/some-program")

Code Signing

Truly secure updates use code signing to verify that the update was issued by a trusted party. To do this, you'll need to generate a public/private key pair. You can do this with openssl, or the equinox.io client (https://equinox.io/client) can easily generate one for you:

# with equinox client
equinox genkey --private-key=private.pem --public-key=public.pem

# with openssl
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -out public.pem -pubout

Once you have your key pair, you can instruct your program to validate its updates with the public key:

const publicKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`

up, err := update.New().VerifySignatureWithPEM(publicKey)
if err != nil {
    return fmt.Errorf("Bad public key: '%v': %v", publicKey, err)
}

Once you've configured your program this way, it will disallow all updates unless they are properly signed. You must now pass in the signature to verify with:

up.VerifySignature(signature).FromUrl("http://dl.example.com/update")

Error Handling and Recovery

To perform an update, the process must be able to read its executable file and to write to the directory that contains its executable file. It can be useful to check whether the process has the necessary permissions to perform an update before trying to apply one. Use the CanUpdate call to provide a useful message to the user if the update can't proceed without elevated permissions:

up := update.New().Target("/etc/hosts")
err := up.CanUpdate()
if err != nil {
    fmt.Printf("Can't update because: '%v'. Try as root or Administrator\n", err)
    return
}
err, errRecover := up.FromUrl("https://example.com/new/hosts")

Although exceedingly unlikely, the update operation itself is not atomic and can fail in such a way that a user's computer is left in an inconsistent state. If that happens, go-update attempts to recover to leave the system in a good state. If the recovery step fails (even more unlikely), a second error, referred to as "errRecover" will be non-nil so that you may inform your users of the bad news. You should handle this case as shown here:

err, errRecover := up.FromUrl("https://example.com/update")
if err != nil {
    fmt.Printf("Update failed: %v\n", err)
    if errRecover != nil {
        fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
        fmt.Printf("Program exectuable may be missing!\n")
    }
}

Subpackages

Sub-package check contains the client functionality for a simple protocol for negotiating whether a new update is available, where it is, and the metadata needed for verifying it.

Sub-package download contains functionality for downloading from an HTTP endpoint while outputting a progress meter and supports resuming partial downloads.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(update io.Reader, opts Options) error

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 ChecksumForBytes

func ChecksumForBytes(source []byte) ([]byte, error)

ChecksumForBytes returns the sha256 checksum for the given bytes

func ChecksumForFile

func ChecksumForFile(path string) ([]byte, error)

ChecksumForFile returns the sha256 checksum for the given file

func ChecksumForReader

func ChecksumForReader(rd io.Reader) ([]byte, error)

ChecksumForReader returns the sha256 checksum for the entire contents of the given reader.

func RollbackError

func RollbackError(err error) error

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.

Types

type Options

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
}

func (*Options) CheckPermissions

func (o *Options) CheckPermissions() error

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 (*Options) SetPublicKeyPEM

func (o *Options) SetPublicKeyPEM(pembytes []byte) error

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.

type PatchType

type PatchType string

The type of a binary patch, if any. Only bsdiff is supported

const (
	PATCHTYPE_BSDIFF PatchType = "bsdiff"
	PATCHTYPE_NONE             = ""
)

type Patcher

type Patcher interface {
	Patch(old io.Reader, new io.Writer, patch io.Reader) error
}

Patcher defines an interface for applying binary patches to an old item to get an updated item.

func NewBSDiffPatcher

func NewBSDiffPatcher() Patcher

NewBSDifferPatcher returns a new Patcher that applies binary patches using the bsdiff algorithm. See http://www.daemonology.net/bsdiff/

type Update

type Update struct {
	// empty string means "path of the current executable"
	TargetPath string

	// type of patch to apply. PATCHTYPE_NONE means "not a patch"
	PatchType

	// sha256 checksum of the new binary to verify against
	Checksum []byte

	// public key to use for signature verification
	PublicKey *rsa.PublicKey

	// signature to use for signature verification
	Signature []byte

	// configurable http client can be passed to download
	HTTPClient *http.Client
}

func New

func New() *Update

New creates a new Update object. A default update object assumes the complete binary content will be used for update (not a patch) and that the intended target is the running executable.

Use this as the start of a chain of calls on the Update object to build up your configuration. Example:

up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)

func (*Update) ApplyPatch

func (u *Update) ApplyPatch(patchType PatchType) *Update

ApplyPatch configures the update to treat the contents of the update as a patch to apply to the existing to target. You must specify the format of the patch. Only PATCHTYPE_BSDIFF is supported at the moment.

func (*Update) CanUpdate

func (u *Update) CanUpdate() (err error)

CanUpdate() 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 (*Update) FromFile

func (u *Update) FromFile(path string) (err error, errRecover error)

FromFile updates the target the contents of the given file.

func (*Update) FromStream

func (u *Update) FromStream(updateWith io.Reader) (err error, errRecover error)

FromStream updates the target file with the contents of the supplied io.Reader.

FromStream performs the following actions to ensure a safe cross-platform update:

1. If configured, applies the contents of the io.Reader as a binary patch.

2. If configured, computes the sha256 checksum and verifies it matches.

3. If configured, verifies the RSA signature with a public key.

4. Creates a new file, /path/to/.target.new with mode 0755 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 rename is successful, deletes /path/to/.target.old, returns no error

8. If the rename fails, attempts to rename /path/to/.target.old back to /path/to/target If this operation fails, it is reported in the errRecover return value so as not to mask the original error that caused the recovery attempt.

On Windows, the removal of /path/to/.target.old always fails, so instead, we just make the old file hidden instead.

func (*Update) FromUrl

func (u *Update) FromUrl(url string) (err error, errRecover error)

FromUrl updates the target with the contents of the given URL.

func (*Update) Target

func (u *Update) Target(path string) *Update

Target configures the update to update the file at the given path. The emptry string means 'the executable file of the running program'.

func (*Update) VerifyChecksum

func (u *Update) VerifyChecksum(checksum []byte) *Update

VerifyChecksum configures the update to verify that the the update has the given sha256 checksum.

func (*Update) VerifySignature

func (u *Update) VerifySignature(signature []byte) *Update

VerifySignature configures the update to verify the given signature of the update. You must also call one of the VerifySignatureWith* functions to specify a public key to use for verification.

func (*Update) VerifySignatureWith

func (u *Update) VerifySignatureWith(publicKey *rsa.PublicKey) *Update

VerifySignatureWith configures the update to use the given RSA public key to verify the update's signature. You must also call VerifySignature() with a signature to check.

You'll probably want to use VerifySignatureWithPEM instead of parsing the public key yourself.

func (*Update) VerifySignatureWithPEM

func (u *Update) VerifySignatureWithPEM(publicKeyPEM []byte) (*Update, error)

VerifySignatureWithPEM configures the update to use the given PEM-formatted RSA public key to verify the update's signature. You must also call VerifySignature() with a signature to check.

A PEM formatted public key typically begins with

-----BEGIN PUBLIC KEY-----

type Verifier

type Verifier interface {
	VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error
}

Verifier defines an interface for verfiying an update's signature with a public key.

func NewDSAVerifier

func NewDSAVerifier() Verifier

NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates.

func NewECDSAVerifier

func NewECDSAVerifier() Verifier

NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates.

func NewRSAVerifier

func NewRSAVerifier() Verifier

NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates.

Directories

Path Synopsis
internal
binarydist
Package binarydist implements binary diff and patch as described on http://www.daemonology.net/bsdiff/.
Package binarydist implements binary diff and patch as described on http://www.daemonology.net/bsdiff/.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL