├── LICENSE ├── README.md ├── apply.go ├── apply_test.go ├── doc.go ├── hide_noop.go ├── hide_windows.go ├── internal ├── binarydist │ ├── License │ ├── Readme.md │ ├── bzip2.go │ ├── common_test.go │ ├── diff.go │ ├── diff_test.go │ ├── doc.go │ ├── encoding.go │ ├── patch.go │ ├── patch_test.go │ ├── seek.go │ ├── sort_test.go │ └── testdata │ │ ├── sample.new │ │ ├── sample.old │ │ └── sample.patch └── osext │ ├── LICENSE │ ├── README.md │ ├── osext.go │ ├── osext_plan9.go │ ├── osext_procfs.go │ ├── osext_sysctl.go │ ├── osext_test.go │ └── osext_windows.go ├── patcher.go └── verifier.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Alan Shreve 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 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) 2 | 3 | Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets) 4 | A program can update itself by replacing its executable file with a new version. 5 | 6 | It provides the flexibility to implement different updating user experiences 7 | like auto-updating, or manual user-initiated updates. It also boasts 8 | advanced features like binary patching and code signing verification. 9 | 10 | Example of updating from a URL: 11 | 12 | ```go 13 | import ( 14 | "fmt" 15 | "net/http" 16 | 17 | "github.com/inconshreveable/go-update" 18 | ) 19 | 20 | func doUpdate(url string) error { 21 | resp, err := http.Get(url) 22 | if err != nil { 23 | return err 24 | } 25 | defer resp.Body.Close() 26 | err := update.Apply(resp.Body, update.Options{}) 27 | if err != nil { 28 | // error handling 29 | } 30 | return err 31 | } 32 | ``` 33 | 34 | ## Features 35 | 36 | - Cross platform support (Windows too!) 37 | - Binary patch application 38 | - Checksum verification 39 | - Code signing verification 40 | - Support for updating arbitrary files 41 | 42 | ## [equinox.io](https://equinox.io) 43 | [equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides: 44 | 45 | - Hosted updates 46 | - Update channels (stable, beta, nightly, ...) 47 | - Dynamically computed binary diffs 48 | - Automatic key generation and code 49 | - Release tooling with proper code signing 50 | - Update/download metrics 51 | 52 | ## API Compatibility Promises 53 | The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor 54 | your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor). 55 | 56 | The `go-update` package makes the following promises about API compatibility: 57 | 1. A list of all API-breaking changes will be documented in this README. 58 | 1. `go-update` will strive for as few API-breaking changes as possible. 59 | 60 | ## API Breaking Changes 61 | - **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`. 62 | - **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`. 63 | 64 | ## License 65 | Apache 66 | -------------------------------------------------------------------------------- /apply.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | 15 | "github.com/inconshreveable/go-update/internal/osext" 16 | ) 17 | 18 | var ( 19 | openFile = os.OpenFile 20 | ) 21 | 22 | // Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader. 23 | // 24 | // Apply performs the following actions to ensure a safe cross-platform update: 25 | // 26 | // 1. If configured, applies the contents of the update io.Reader as a binary patch. 27 | // 28 | // 2. If configured, computes the checksum of the new executable and verifies it matches. 29 | // 30 | // 3. If configured, verifies the signature with a public key. 31 | // 32 | // 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file 33 | // 34 | // 5. Renames /path/to/target to /path/to/.target.old 35 | // 36 | // 6. Renames /path/to/.target.new to /path/to/target 37 | // 38 | // 7. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, 39 | // the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. 40 | // 41 | // 8. If the final rename fails, attempts to roll back by renaming /path/to/.target.old 42 | // back to /path/to/target. 43 | // 44 | // If the roll back operation fails, the file system is left in an inconsistent state (betweet steps 5 and 6) where 45 | // there is no new executable file and the old executable file could not be be moved to its original location. In this 46 | // case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether 47 | // the rollback failed by calling RollbackError, see the documentation on that function for additional detail. 48 | func Apply(update io.Reader, opts Options) error { 49 | // validate 50 | verify := false 51 | switch { 52 | case opts.Signature != nil && opts.PublicKey != nil: 53 | // okay 54 | verify = true 55 | case opts.Signature != nil: 56 | return errors.New("no public key to verify signature with") 57 | case opts.PublicKey != nil: 58 | return errors.New("No signature to verify with") 59 | } 60 | 61 | // set defaults 62 | if opts.Hash == 0 { 63 | opts.Hash = crypto.SHA256 64 | } 65 | if opts.Verifier == nil { 66 | opts.Verifier = NewECDSAVerifier() 67 | } 68 | if opts.TargetMode == 0 { 69 | opts.TargetMode = 0755 70 | } 71 | 72 | // get target path 73 | var err error 74 | opts.TargetPath, err = opts.getPath() 75 | if err != nil { 76 | return err 77 | } 78 | 79 | var newBytes []byte 80 | if opts.Patcher != nil { 81 | if newBytes, err = opts.applyPatch(update); err != nil { 82 | return err 83 | } 84 | } else { 85 | // no patch to apply, go on through 86 | if newBytes, err = ioutil.ReadAll(update); err != nil { 87 | return err 88 | } 89 | } 90 | 91 | // verify checksum if requested 92 | if opts.Checksum != nil { 93 | if err = opts.verifyChecksum(newBytes); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | if verify { 99 | if err = opts.verifySignature(newBytes); err != nil { 100 | return err 101 | } 102 | } 103 | 104 | // get the directory the executable exists in 105 | updateDir := filepath.Dir(opts.TargetPath) 106 | filename := filepath.Base(opts.TargetPath) 107 | 108 | // Copy the contents of newbinary to a new executable file 109 | newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) 110 | fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.TargetMode) 111 | if err != nil { 112 | return err 113 | } 114 | defer fp.Close() 115 | 116 | _, err = io.Copy(fp, bytes.NewReader(newBytes)) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | // if we don't call fp.Close(), windows won't let us move the new executable 122 | // because the file will still be "in use" 123 | fp.Close() 124 | 125 | // this is where we'll move the executable to so that we can swap in the updated replacement 126 | oldPath := opts.OldSavePath 127 | removeOld := opts.OldSavePath == "" 128 | if removeOld { 129 | oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) 130 | } 131 | 132 | // delete any existing old exec file - this is necessary on Windows for two reasons: 133 | // 1. after a successful update, Windows can't remove the .old file because the process is still running 134 | // 2. windows rename operations fail if the destination file already exists 135 | _ = os.Remove(oldPath) 136 | 137 | // move the existing executable to a new file in the same directory 138 | err = os.Rename(opts.TargetPath, oldPath) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | // move the new exectuable in to become the new program 144 | err = os.Rename(newPath, opts.TargetPath) 145 | 146 | if err != nil { 147 | // move unsuccessful 148 | // 149 | // The filesystem is now in a bad state. We have successfully 150 | // moved the existing binary to a new location, but we couldn't move the new 151 | // binary to take its place. That means there is no file where the current executable binary 152 | // used to be! 153 | // Try to rollback by restoring the old binary to its original path. 154 | rerr := os.Rename(oldPath, opts.TargetPath) 155 | if rerr != nil { 156 | return &rollbackErr{err, rerr} 157 | } 158 | 159 | return err 160 | } 161 | 162 | // move successful, remove the old binary if needed 163 | if removeOld { 164 | errRemove := os.Remove(oldPath) 165 | 166 | // windows has trouble with removing old binaries, so hide it instead 167 | if errRemove != nil { 168 | _ = hideFile(oldPath) 169 | } 170 | } 171 | 172 | return nil 173 | } 174 | 175 | // RollbackError takes an error value returned by Apply and returns the error, if any, 176 | // that occurred when attempting to roll back from a failed update. Applications should 177 | // always call this function on any non-nil errors returned by Apply. 178 | // 179 | // If no rollback was needed or if the rollback was successful, RollbackError returns nil, 180 | // otherwise it returns the error encountered when trying to roll back. 181 | func RollbackError(err error) error { 182 | if err == nil { 183 | return nil 184 | } 185 | if rerr, ok := err.(*rollbackErr); ok { 186 | return rerr.rollbackErr 187 | } 188 | return nil 189 | } 190 | 191 | type rollbackErr struct { 192 | error // original error 193 | rollbackErr error // error encountered while rolling back 194 | } 195 | 196 | type Options struct { 197 | // TargetPath defines the path to the file to update. 198 | // The emptry string means 'the executable file of the running program'. 199 | TargetPath string 200 | 201 | // Create TargetPath replacement with this file mode. If zero, defaults to 0755. 202 | TargetMode os.FileMode 203 | 204 | // Checksum of the new binary to verify against. If nil, no checksum or signature verification is done. 205 | Checksum []byte 206 | 207 | // Public key to use for signature verification. If nil, no signature verification is done. 208 | PublicKey crypto.PublicKey 209 | 210 | // Signature to verify the updated file. If nil, no signature verification is done. 211 | Signature []byte 212 | 213 | // Pluggable signature verification algorithm. If nil, ECDSA is used. 214 | Verifier Verifier 215 | 216 | // Use this hash function to generate the checksum. If not set, SHA256 is used. 217 | Hash crypto.Hash 218 | 219 | // If nil, treat the update as a complete replacement for the contents of the file at TargetPath. 220 | // If non-nil, treat the update contents as a patch and use this object to apply the patch. 221 | Patcher Patcher 222 | 223 | // Store the old executable file at this path after a successful update. 224 | // The empty string means the old executable file will be removed after the update. 225 | OldSavePath string 226 | } 227 | 228 | // CheckPermissions determines whether the process has the correct permissions to 229 | // perform the requested update. If the update can proceed, it returns nil, otherwise 230 | // it returns the error that would occur if an update were attempted. 231 | func (o *Options) CheckPermissions() error { 232 | // get the directory the file exists in 233 | path, err := o.getPath() 234 | if err != nil { 235 | return err 236 | } 237 | 238 | fileDir := filepath.Dir(path) 239 | fileName := filepath.Base(path) 240 | 241 | // attempt to open a file in the file's directory 242 | newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName)) 243 | fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.TargetMode) 244 | if err != nil { 245 | return err 246 | } 247 | fp.Close() 248 | 249 | _ = os.Remove(newPath) 250 | return nil 251 | } 252 | 253 | // SetPublicKeyPEM is a convenience method to set the PublicKey property 254 | // used for checking a completed update's signature by parsing a 255 | // Public Key formatted as PEM data. 256 | func (o *Options) SetPublicKeyPEM(pembytes []byte) error { 257 | block, _ := pem.Decode(pembytes) 258 | if block == nil { 259 | return errors.New("couldn't parse PEM data") 260 | } 261 | 262 | pub, err := x509.ParsePKIXPublicKey(block.Bytes) 263 | if err != nil { 264 | return err 265 | } 266 | o.PublicKey = pub 267 | return nil 268 | } 269 | 270 | func (o *Options) getPath() (string, error) { 271 | if o.TargetPath == "" { 272 | return osext.Executable() 273 | } else { 274 | return o.TargetPath, nil 275 | } 276 | } 277 | 278 | func (o *Options) applyPatch(patch io.Reader) ([]byte, error) { 279 | // open the file to patch 280 | old, err := os.Open(o.TargetPath) 281 | if err != nil { 282 | return nil, err 283 | } 284 | defer old.Close() 285 | 286 | // apply the patch 287 | var applied bytes.Buffer 288 | if err = o.Patcher.Patch(old, &applied, patch); err != nil { 289 | return nil, err 290 | } 291 | 292 | return applied.Bytes(), nil 293 | } 294 | 295 | func (o *Options) verifyChecksum(updated []byte) error { 296 | checksum, err := checksumFor(o.Hash, updated) 297 | if err != nil { 298 | return err 299 | } 300 | 301 | if !bytes.Equal(o.Checksum, checksum) { 302 | return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum) 303 | } 304 | return nil 305 | } 306 | 307 | func (o *Options) verifySignature(updated []byte) error { 308 | checksum, err := checksumFor(o.Hash, updated) 309 | if err != nil { 310 | return err 311 | } 312 | return o.Verifier.VerifySignature(checksum, o.Signature, o.Hash, o.PublicKey) 313 | } 314 | 315 | func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) { 316 | if !h.Available() { 317 | return nil, errors.New("requested hash function not available") 318 | } 319 | hash := h.New() 320 | hash.Write(payload) // guaranteed not to error 321 | return hash.Sum([]byte{}), nil 322 | } 323 | -------------------------------------------------------------------------------- /apply_test.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/pem" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "testing" 14 | 15 | "github.com/inconshreveable/go-update/internal/binarydist" 16 | ) 17 | 18 | var ( 19 | oldFile = []byte{0xDE, 0xAD, 0xBE, 0xEF} 20 | newFile = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06} 21 | newFileChecksum = sha256.Sum256(newFile) 22 | ) 23 | 24 | func cleanup(path string) { 25 | os.Remove(path) 26 | os.Remove(fmt.Sprintf(".%s.new", path)) 27 | } 28 | 29 | // we write with a separate name for each test so that we can run them in parallel 30 | func writeOldFile(path string, t *testing.T) { 31 | if err := ioutil.WriteFile(path, oldFile, 0777); err != nil { 32 | t.Fatalf("Failed to write file for testing preparation: %v", err) 33 | } 34 | } 35 | 36 | func validateUpdate(path string, err error, t *testing.T) { 37 | if err != nil { 38 | t.Fatalf("Failed to update: %v", err) 39 | } 40 | 41 | buf, err := ioutil.ReadFile(path) 42 | if err != nil { 43 | t.Fatalf("Failed to read file post-update: %v", err) 44 | } 45 | 46 | if !bytes.Equal(buf, newFile) { 47 | t.Fatalf("File was not updated! Bytes read: %v, Bytes expected: %v", buf, newFile) 48 | } 49 | } 50 | 51 | func TestApplySimple(t *testing.T) { 52 | fName := "TestApplySimple" 53 | defer cleanup(fName) 54 | writeOldFile(fName, t) 55 | 56 | err := Apply(bytes.NewReader(newFile), Options{ 57 | TargetPath: fName, 58 | }) 59 | validateUpdate(fName, err, t) 60 | } 61 | 62 | func TestApplyOldSavePath(t *testing.T) { 63 | fName := "TestApplyOldSavePath" 64 | defer cleanup(fName) 65 | writeOldFile(fName, t) 66 | 67 | oldfName := "OldSavePath" 68 | 69 | err := Apply(bytes.NewReader(newFile), Options{ 70 | TargetPath: fName, 71 | OldSavePath: oldfName, 72 | }) 73 | validateUpdate(fName, err, t) 74 | 75 | if _, err := os.Stat(oldfName); os.IsNotExist(err) { 76 | t.Fatalf("Failed to find the old file: %v", err) 77 | } 78 | 79 | cleanup(oldfName) 80 | } 81 | 82 | func TestVerifyChecksum(t *testing.T) { 83 | fName := "TestVerifyChecksum" 84 | defer cleanup(fName) 85 | writeOldFile(fName, t) 86 | 87 | err := Apply(bytes.NewReader(newFile), Options{ 88 | TargetPath: fName, 89 | Checksum: newFileChecksum[:], 90 | }) 91 | validateUpdate(fName, err, t) 92 | } 93 | 94 | func TestVerifyChecksumNegative(t *testing.T) { 95 | fName := "TestVerifyChecksumNegative" 96 | defer cleanup(fName) 97 | writeOldFile(fName, t) 98 | 99 | badChecksum := []byte{0x0A, 0x0B, 0x0C, 0xFF} 100 | err := Apply(bytes.NewReader(newFile), Options{ 101 | TargetPath: fName, 102 | Checksum: badChecksum, 103 | }) 104 | if err == nil { 105 | t.Fatalf("Failed to detect bad checksum!") 106 | } 107 | } 108 | 109 | func TestApplyPatch(t *testing.T) { 110 | fName := "TestApplyPatch" 111 | defer cleanup(fName) 112 | writeOldFile(fName, t) 113 | 114 | patch := new(bytes.Buffer) 115 | err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(newFile), patch) 116 | if err != nil { 117 | t.Fatalf("Failed to create patch: %v", err) 118 | } 119 | 120 | err = Apply(patch, Options{ 121 | TargetPath: fName, 122 | Patcher: NewBSDiffPatcher(), 123 | }) 124 | validateUpdate(fName, err, t) 125 | } 126 | 127 | func TestCorruptPatch(t *testing.T) { 128 | fName := "TestCorruptPatch" 129 | defer cleanup(fName) 130 | writeOldFile(fName, t) 131 | 132 | badPatch := []byte{0x44, 0x38, 0x86, 0x3c, 0x4f, 0x8d, 0x26, 0x54, 0xb, 0x11, 0xce, 0xfe, 0xc1, 0xc0, 0xf8, 0x31, 0x38, 0xa0, 0x12, 0x1a, 0xa2, 0x57, 0x2a, 0xe1, 0x3a, 0x48, 0x62, 0x40, 0x2b, 0x81, 0x12, 0xb1, 0x21, 0xa5, 0x16, 0xed, 0x73, 0xd6, 0x54, 0x84, 0x29, 0xa6, 0xd6, 0xb2, 0x1b, 0xfb, 0xe6, 0xbe, 0x7b, 0x70} 133 | err := Apply(bytes.NewReader(badPatch), Options{ 134 | TargetPath: fName, 135 | Patcher: NewBSDiffPatcher(), 136 | }) 137 | if err == nil { 138 | t.Fatalf("Failed to detect corrupt patch!") 139 | } 140 | } 141 | 142 | func TestVerifyChecksumPatchNegative(t *testing.T) { 143 | fName := "TestVerifyChecksumPatchNegative" 144 | defer cleanup(fName) 145 | writeOldFile(fName, t) 146 | 147 | patch := new(bytes.Buffer) 148 | anotherFile := []byte{0x77, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66} 149 | err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(anotherFile), patch) 150 | if err != nil { 151 | t.Fatalf("Failed to create patch: %v", err) 152 | } 153 | 154 | err = Apply(patch, Options{ 155 | TargetPath: fName, 156 | Checksum: newFileChecksum[:], 157 | Patcher: NewBSDiffPatcher(), 158 | }) 159 | if err == nil { 160 | t.Fatalf("Failed to detect patch to wrong file!") 161 | } 162 | } 163 | 164 | const ecdsaPublicKey = ` 165 | -----BEGIN PUBLIC KEY----- 166 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEL8ThbSyEucsCxnd4dCZR2hIy5nea54ko 167 | O+jUUfIjkvwhCWzASm0lpCVdVpXKZXIe+NZ+44RQRv3+OqJkCCGzUgJkPNI3lxdG 168 | 9zu8rbrnxISV06VQ8No7Ei9wiTpqmTBB 169 | -----END PUBLIC KEY----- 170 | ` 171 | 172 | const ecdsaPrivateKey = ` 173 | -----BEGIN EC PRIVATE KEY----- 174 | MIGkAgEBBDBttCB/1NOY4T+WrG4FSV49Ayn3gK1DNzfGaJ01JUXeiNFCWQM2pqpU 175 | om8ATPP/dkegBwYFK4EEACKhZANiAAQvxOFtLIS5ywLGd3h0JlHaEjLmd5rniSg7 176 | 6NRR8iOS/CEJbMBKbSWkJV1Wlcplch741n7jhFBG/f46omQIIbNSAmQ80jeXF0b3 177 | O7ytuufEhJXTpVDw2jsSL3CJOmqZMEE= 178 | -----END EC PRIVATE KEY----- 179 | ` 180 | 181 | const rsaPublicKey = ` 182 | -----BEGIN PUBLIC KEY----- 183 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxSWmu7trWKAwDFjiCN2D 184 | Tk2jj2sgcr/CMlI4cSSiIOHrXCFxP1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKab 185 | b9ead+kD0kxk7i2bFYvKX43oq66IW0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4 186 | y20C59dPr9Dpcz8DZkdLsBV6YKF6Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjT 187 | x4xRnjgTRRRlZvRtALHMUkIChgxDOhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv5 188 | 5fhJ08Rz7mmZmtH5JxTK5XTquo59sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7Nrf 189 | fQIDAQAB 190 | -----END PUBLIC KEY-----` 191 | 192 | const rsaPrivateKey = ` 193 | -----BEGIN RSA PRIVATE KEY----- 194 | MIIEogIBAAKCAQEAxSWmu7trWKAwDFjiCN2DTk2jj2sgcr/CMlI4cSSiIOHrXCFx 195 | P1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKabb9ead+kD0kxk7i2bFYvKX43oq66I 196 | W0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4y20C59dPr9Dpcz8DZkdLsBV6YKF6 197 | Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjTx4xRnjgTRRRlZvRtALHMUkIChgxD 198 | OhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv55fhJ08Rz7mmZmtH5JxTK5XTquo59 199 | sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7NrffQIDAQABAoIBAAkN+6RvrTR61voa 200 | Mvd5RQiZpEN4Bht/Fyo8gH8h0Zh1B9xJZOwlmMZLS5fdtHlfLEhR8qSrGDBL61vq 201 | I8KkhEsUufF78EL+YzxVN+Q7cWYGHIOWFokqza7hzpSxUQO6lPOMQ1eIZaNueJTB 202 | Zu07/47ISPPg/bXzgGVcpYlTCPTjUwKjtfyMqvX9AD7fIyYRm6zfE7EHj1J2sBFt 203 | Yz1OGELg6HfJwXfpnPfBvftD0hWGzJ78Bp71fPJe6n5gnqmSqRvrcXNWFnH/yqkN 204 | d6vPIxD6Z3LjvyZpkA7JillLva2L/zcIFhg4HZvQnWd8/PpDnUDonu36hcj4SC5j 205 | W4aVPLkCgYEA4XzNKWxqYcajzFGZeSxlRHupSAl2MT7Cc5085MmE7dd31wK2T8O4 206 | n7N4bkm/rjTbX85NsfWdKtWb6mpp8W3VlLP0rp4a/12OicVOkg4pv9LZDmY0sRlE 207 | YuDJk1FeCZ50UrwTZI3rZ9IhZHhkgVA6uWAs7tYndONkxNHG0pjqs4sCgYEA39MZ 208 | JwMqo3qsPntpgP940cCLflEsjS9hYNO3+Sv8Dq3P0HLVhBYajJnotf8VuU0fsQZG 209 | grmtVn1yThFbMq7X1oY4F0XBA+paSiU18c4YyUnwax2u4sw9U/Q9tmQUZad5+ueT 210 | qriMBwGv+ewO+nQxqvAsMUmemrVzrfwA5Oct+hcCgYAfiyXoNZJsOy2O15twqBVC 211 | j0oPGcO+/9iT89sg5lACNbI+EdMPNYIOVTzzsL1v0VUfAe08h++Enn1BPcG0VHkc 212 | ZFBGXTfJoXzfKQrkw7ZzbzuOGB4m6DH44xlP0oIlNlVvfX/5ASF9VJf3RiBJNsAA 213 | TsP6ZVr/rw/ZuL7nlxy+IQKBgDhL/HOXlE3yOQiuOec8WsNHTs7C1BXe6PtVxVxi 214 | 988pYK/pclL6zEq5G5NLSceF4obAMVQIJ9UtUGbabrncyGUo9UrFPLsjYvprSZo8 215 | YHegpVwL50UcYgCP2kXZ/ldjPIcjYDz8lhvdDMor2cidGTEJn9P11HLNWP9V91Ob 216 | 4jCZAoGAPNRSC5cC8iP/9j+s2/kdkfWJiNaolPYAUrmrkL6H39PYYZM5tnhaIYJV 217 | Oh9AgABamU0eb3p3vXTISClVgV7ifq1HyZ7BSUhMfaY2Jk/s3sUHCWFxPZe9sgEG 218 | KinIY/373KIkIV/5g4h2v1w330IWcfptxKcY/Er3DJr38f695GE= 219 | -----END RSA PRIVATE KEY-----` 220 | 221 | func signec(privatePEM string, source []byte, t *testing.T) []byte { 222 | parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParseECPrivateKey(p) } 223 | return sign(parseFn, privatePEM, source, t) 224 | } 225 | 226 | func signrsa(privatePEM string, source []byte, t *testing.T) []byte { 227 | parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParsePKCS1PrivateKey(p) } 228 | return sign(parseFn, privatePEM, source, t) 229 | } 230 | 231 | func sign(parsePrivKey func([]byte) (crypto.Signer, error), privatePEM string, source []byte, t *testing.T) []byte { 232 | block, _ := pem.Decode([]byte(privatePEM)) 233 | if block == nil { 234 | t.Fatalf("Failed to parse private key PEM") 235 | } 236 | 237 | priv, err := parsePrivKey(block.Bytes) 238 | if err != nil { 239 | t.Fatalf("Failed to parse private key DER: %v", err) 240 | } 241 | 242 | checksum := sha256.Sum256(source) 243 | sig, err := priv.Sign(rand.Reader, checksum[:], crypto.SHA256) 244 | if err != nil { 245 | t.Fatalf("Failed to sign: %v", sig) 246 | } 247 | 248 | return sig 249 | } 250 | 251 | func TestVerifyECSignature(t *testing.T) { 252 | fName := "TestVerifyECSignature" 253 | defer cleanup(fName) 254 | writeOldFile(fName, t) 255 | 256 | opts := Options{TargetPath: fName} 257 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)) 258 | if err != nil { 259 | t.Fatalf("Could not parse public key: %v", err) 260 | } 261 | 262 | opts.Signature = signec(ecdsaPrivateKey, newFile, t) 263 | err = Apply(bytes.NewReader(newFile), opts) 264 | validateUpdate(fName, err, t) 265 | } 266 | 267 | func TestVerifyRSASignature(t *testing.T) { 268 | fName := "TestVerifyRSASignature" 269 | defer cleanup(fName) 270 | writeOldFile(fName, t) 271 | 272 | opts := Options{ 273 | TargetPath: fName, 274 | Verifier: NewRSAVerifier(), 275 | } 276 | err := opts.SetPublicKeyPEM([]byte(rsaPublicKey)) 277 | if err != nil { 278 | t.Fatalf("Could not parse public key: %v", err) 279 | } 280 | 281 | opts.Signature = signrsa(rsaPrivateKey, newFile, t) 282 | err = Apply(bytes.NewReader(newFile), opts) 283 | validateUpdate(fName, err, t) 284 | } 285 | 286 | func TestVerifyFailBadSignature(t *testing.T) { 287 | fName := "TestVerifyFailBadSignature" 288 | defer cleanup(fName) 289 | writeOldFile(fName, t) 290 | 291 | opts := Options{ 292 | TargetPath: fName, 293 | Signature: []byte{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}, 294 | } 295 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)) 296 | if err != nil { 297 | t.Fatalf("Could not parse public key: %v", err) 298 | } 299 | 300 | err = Apply(bytes.NewReader(newFile), opts) 301 | if err == nil { 302 | t.Fatalf("Did not fail with bad signature") 303 | } 304 | } 305 | 306 | func TestVerifyFailNoSignature(t *testing.T) { 307 | fName := "TestVerifySignatureWithPEM" 308 | defer cleanup(fName) 309 | writeOldFile(fName, t) 310 | 311 | opts := Options{TargetPath: fName} 312 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)) 313 | if err != nil { 314 | t.Fatalf("Could not parse public key: %v", err) 315 | } 316 | 317 | err = Apply(bytes.NewReader(newFile), opts) 318 | if err == nil { 319 | t.Fatalf("Did not fail with empty signature") 320 | } 321 | } 322 | 323 | const wrongKey = ` 324 | -----BEGIN EC PRIVATE KEY----- 325 | MIGkAgEBBDBzqYp6N2s8YWYifBjS03/fFfmGeIPcxQEi+bbFeekIYt8NIKIkhD+r 326 | hpaIwSmot+qgBwYFK4EEACKhZANiAAR0EC8Usbkc4k30frfEB2ECmsIghu9DJSqE 327 | RbH7jfq2ULNv8tN/clRjxf2YXgp+iP3SQF1R1EYERKpWr8I57pgfIZtoZXjwpbQC 328 | VBbP/Ff+05HOqwPC7rJMy1VAJLKg7Cw= 329 | -----END EC PRIVATE KEY----- 330 | ` 331 | 332 | func TestVerifyFailWrongSignature(t *testing.T) { 333 | fName := "TestVerifyFailWrongSignature" 334 | defer cleanup(fName) 335 | writeOldFile(fName, t) 336 | 337 | opts := Options{TargetPath: fName} 338 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)) 339 | if err != nil { 340 | t.Fatalf("Could not parse public key: %v", err) 341 | } 342 | 343 | opts.Signature = signec(wrongKey, newFile, t) 344 | err = Apply(bytes.NewReader(newFile), opts) 345 | if err == nil { 346 | t.Fatalf("Verified an update that was signed by an untrusted key!") 347 | } 348 | } 349 | 350 | func TestSignatureButNoPublicKey(t *testing.T) { 351 | fName := "TestSignatureButNoPublicKey" 352 | defer cleanup(fName) 353 | writeOldFile(fName, t) 354 | 355 | err := Apply(bytes.NewReader(newFile), Options{ 356 | TargetPath: fName, 357 | Signature: signec(ecdsaPrivateKey, newFile, t), 358 | }) 359 | if err == nil { 360 | t.Fatalf("Allowed an update with a signautre verification when no public key was specified!") 361 | } 362 | } 363 | 364 | func TestPublicKeyButNoSignature(t *testing.T) { 365 | fName := "TestPublicKeyButNoSignature" 366 | defer cleanup(fName) 367 | writeOldFile(fName, t) 368 | 369 | opts := Options{TargetPath: fName} 370 | if err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)); err != nil { 371 | t.Fatalf("Could not parse public key: %v", err) 372 | } 373 | err := Apply(bytes.NewReader(newFile), opts) 374 | if err == nil { 375 | t.Fatalf("Allowed an update with no signautre when a public key was specified!") 376 | } 377 | } 378 | 379 | func TestWriteError(t *testing.T) { 380 | fName := "TestWriteError" 381 | defer cleanup(fName) 382 | writeOldFile(fName, t) 383 | 384 | openFile = func(name string, flags int, perm os.FileMode) (*os.File, error) { 385 | f, err := os.OpenFile(name, flags, perm) 386 | 387 | // simulate Write() error by closing the file prematurely 388 | f.Close() 389 | 390 | return f, err 391 | } 392 | defer func() { 393 | openFile = os.OpenFile 394 | }() 395 | 396 | err := Apply(bytes.NewReader(newFile), Options{TargetPath: fName}) 397 | if err == nil { 398 | t.Fatalf("Allowed an update to an empty file") 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets). 3 | 4 | For complete updating solutions please see Equinox (https://equinox.io) and go-tuf (https://github.com/flynn/go-tuf). 5 | 6 | Basic Example 7 | 8 | This example shows how to update a program remotely from a URL. 9 | 10 | import ( 11 | "fmt" 12 | "net/http" 13 | 14 | "github.com/inconshreveable/go-update" 15 | ) 16 | 17 | func doUpdate(url string) error { 18 | // request the new file 19 | resp, err := http.Get(url) 20 | if err != nil { 21 | return err 22 | } 23 | defer resp.Body.Close() 24 | err := update.Apply(resp.Body, update.Options{}) 25 | if err != nil { 26 | if rerr := update.RollbackError(err); rerr != nil { 27 | fmt.Println("Failed to rollback from bad update: %v", rerr) 28 | } 29 | } 30 | return err 31 | } 32 | 33 | 34 | Binary Patching 35 | 36 | Go binaries can often be large. It can be advantageous to only ship a binary patch to a client 37 | instead of the complete program text of a new version. 38 | 39 | This example shows how to update a program with a bsdiff binary patch. Other patch formats 40 | may be applied by implementing the Patcher interface. 41 | 42 | import ( 43 | "encoding/hex" 44 | "io" 45 | 46 | "github.com/inconshreveable/go-update" 47 | ) 48 | 49 | func updateWithPatch(patch io.Reader) error { 50 | err := update.Apply(patch, update.Options{ 51 | Patcher: update.NewBSDiffPatcher() 52 | }) 53 | if err != nil { 54 | // error handling 55 | } 56 | return err 57 | } 58 | 59 | Checksum Verification 60 | 61 | Updating executable code on a computer can be a dangerous operation unless you 62 | take the appropriate steps to guarantee the authenticity of the new code. While 63 | checksum verification is important, it should always be combined with signature 64 | verification (next section) to guarantee that the code came from a trusted party. 65 | 66 | go-update validates SHA256 checksums by default, but this is pluggable via the Hash 67 | property on the Options struct. 68 | 69 | This example shows how to guarantee that the newly-updated binary is verified to 70 | have an appropriate checksum (that was otherwise retrived via a secure channel) 71 | specified as a hex string. 72 | 73 | import ( 74 | "crypto" 75 | _ "crypto/sha256" 76 | "encoding/hex" 77 | "io" 78 | 79 | "github.com/inconshreveable/go-update" 80 | ) 81 | 82 | func updateWithChecksum(binary io.Reader, hexChecksum string) error { 83 | checksum, err := hex.DecodeString(hexChecksum) 84 | if err != nil { 85 | return err 86 | } 87 | err = update.Apply(binary, update.Options{ 88 | Hash: crypto.SHA256, // this is the default, you don't need to specify it 89 | Checksum: checksum, 90 | }) 91 | if err != nil { 92 | // error handling 93 | } 94 | return err 95 | } 96 | 97 | Cryptographic Signature Verification 98 | 99 | Cryptographic verification of new code from an update is an extremely important way to guarantee the 100 | security and integrity of your updates. 101 | 102 | Verification is performed by validating the signature of a hash of the new file. This 103 | means nothing changes if you apply your update with a patch. 104 | 105 | This example shows how to add signature verification to your updates. To make all of this work 106 | an application distributor must first create a public/private key pair and embed the public key 107 | into their application. When they issue a new release, the issuer must sign the new executable file 108 | with the private key and distribute the signature along with the update. 109 | 110 | import ( 111 | "crypto" 112 | _ "crypto/sha256" 113 | "encoding/hex" 114 | "io" 115 | 116 | "github.com/inconshreveable/go-update" 117 | ) 118 | 119 | var publicKey = []byte(` 120 | -----BEGIN PUBLIC KEY----- 121 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx 122 | MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ== 123 | -----END PUBLIC KEY----- 124 | `) 125 | 126 | func verifiedUpdate(binary io.Reader, hexChecksum, hexSignature string) { 127 | checksum, err := hex.DecodeString(hexChecksum) 128 | if err != nil { 129 | return err 130 | } 131 | signature, err := hex.DecodeString(hexSignature) 132 | if err != nil { 133 | return err 134 | } 135 | opts := update.Options{ 136 | Checksum: checksum, 137 | Signature: signature, 138 | Hash: crypto.SHA256, // this is the default, you don't need to specify it 139 | Verifier: update.NewECDSAVerifier(), // this is the default, you don't need to specify it 140 | } 141 | err = opts.SetPublicKeyPEM(publicKey) 142 | if err != nil { 143 | return err 144 | } 145 | err = update.Apply(binary, opts) 146 | if err != nil { 147 | // error handling 148 | } 149 | return err 150 | } 151 | 152 | 153 | Building Single-File Go Binaries 154 | 155 | In order to update a Go application with go-update, you must distributed it as a single executable. 156 | This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates). 157 | In order to update applications like these, you'll want to make sure to embed those asset files into 158 | the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata 159 | 160 | Non-Goals 161 | 162 | Mechanisms and protocols for determining whether an update should be applied and, if so, which one are 163 | out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io) 164 | for more complete solutions. 165 | 166 | go-update only works for self-updating applications that are distributed as a single binary, i.e. 167 | applications that do not have additional assets or dependency files. 168 | Updating application that are distributed as mutliple on-disk files is out of scope, although this 169 | may change in future versions of this library. 170 | 171 | */ 172 | package update 173 | -------------------------------------------------------------------------------- /hide_noop.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package update 4 | 5 | func hideFile(path string) error { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /hide_windows.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | func hideFile(path string) error { 9 | kernel32 := syscall.NewLazyDLL("kernel32.dll") 10 | setFileAttributes := kernel32.NewProc("SetFileAttributesW") 11 | 12 | r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2) 13 | 14 | if r1 == 0 { 15 | return err 16 | } else { 17 | return nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/binarydist/License: -------------------------------------------------------------------------------- 1 | Copyright 2012 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /internal/binarydist/Readme.md: -------------------------------------------------------------------------------- 1 | # binarydist 2 | 3 | Package binarydist implements binary diff and patch as described on 4 | . It reads and writes files 5 | compatible with the tools there. 6 | 7 | Documentation at . 8 | -------------------------------------------------------------------------------- /internal/binarydist/bzip2.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "io" 5 | "os/exec" 6 | ) 7 | 8 | type bzip2Writer struct { 9 | c *exec.Cmd 10 | w io.WriteCloser 11 | } 12 | 13 | func (w bzip2Writer) Write(b []byte) (int, error) { 14 | return w.w.Write(b) 15 | } 16 | 17 | func (w bzip2Writer) Close() error { 18 | if err := w.w.Close(); err != nil { 19 | return err 20 | } 21 | return w.c.Wait() 22 | } 23 | 24 | // Package compress/bzip2 implements only decompression, 25 | // so we'll fake it by running bzip2 in another process. 26 | func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { 27 | var bw bzip2Writer 28 | bw.c = exec.Command("bzip2", "-c") 29 | bw.c.Stdout = w 30 | 31 | if bw.w, err = bw.c.StdinPipe(); err != nil { 32 | return nil, err 33 | } 34 | 35 | if err = bw.c.Start(); err != nil { 36 | return nil, err 37 | } 38 | 39 | return bw, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/binarydist/common_test.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | func mustOpen(path string) *os.File { 11 | f, err := os.Open(path) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | return f 17 | } 18 | 19 | func mustReadAll(r io.Reader) []byte { 20 | b, err := ioutil.ReadAll(r) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return b 25 | } 26 | 27 | func fileCmp(a, b *os.File) int64 { 28 | sa, err := a.Seek(0, 2) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | sb, err := b.Seek(0, 2) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | if sa != sb { 39 | return sa 40 | } 41 | 42 | _, err = a.Seek(0, 0) 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | _, err = b.Seek(0, 0) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | pa, err := ioutil.ReadAll(a) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | pb, err := ioutil.ReadAll(b) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | for i := range pa { 63 | if pa[i] != pb[i] { 64 | return int64(i) 65 | } 66 | } 67 | return -1 68 | } 69 | 70 | func mustWriteRandFile(path string, size int) *os.File { 71 | p := make([]byte, size) 72 | _, err := rand.Read(p) 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | f, err := os.Create(path) 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | _, err = f.Write(p) 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | _, err = f.Seek(0, 0) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | return f 93 | } 94 | -------------------------------------------------------------------------------- /internal/binarydist/diff.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "io/ioutil" 8 | ) 9 | 10 | func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } 11 | 12 | func split(I, V []int, start, length, h int) { 13 | var i, j, k, x, jj, kk int 14 | 15 | if length < 16 { 16 | for k = start; k < start+length; k += j { 17 | j = 1 18 | x = V[I[k]+h] 19 | for i = 1; k+i < start+length; i++ { 20 | if V[I[k+i]+h] < x { 21 | x = V[I[k+i]+h] 22 | j = 0 23 | } 24 | if V[I[k+i]+h] == x { 25 | swap(I, k+i, k+j) 26 | j++ 27 | } 28 | } 29 | for i = 0; i < j; i++ { 30 | V[I[k+i]] = k + j - 1 31 | } 32 | if j == 1 { 33 | I[k] = -1 34 | } 35 | } 36 | return 37 | } 38 | 39 | x = V[I[start+length/2]+h] 40 | jj = 0 41 | kk = 0 42 | for i = start; i < start+length; i++ { 43 | if V[I[i]+h] < x { 44 | jj++ 45 | } 46 | if V[I[i]+h] == x { 47 | kk++ 48 | } 49 | } 50 | jj += start 51 | kk += jj 52 | 53 | i = start 54 | j = 0 55 | k = 0 56 | for i < jj { 57 | if V[I[i]+h] < x { 58 | i++ 59 | } else if V[I[i]+h] == x { 60 | swap(I, i, jj+j) 61 | j++ 62 | } else { 63 | swap(I, i, kk+k) 64 | k++ 65 | } 66 | } 67 | 68 | for jj+j < kk { 69 | if V[I[jj+j]+h] == x { 70 | j++ 71 | } else { 72 | swap(I, jj+j, kk+k) 73 | k++ 74 | } 75 | } 76 | 77 | if jj > start { 78 | split(I, V, start, jj-start, h) 79 | } 80 | 81 | for i = 0; i < kk-jj; i++ { 82 | V[I[jj+i]] = kk - 1 83 | } 84 | if jj == kk-1 { 85 | I[jj] = -1 86 | } 87 | 88 | if start+length > kk { 89 | split(I, V, kk, start+length-kk, h) 90 | } 91 | } 92 | 93 | func qsufsort(obuf []byte) []int { 94 | var buckets [256]int 95 | var i, h int 96 | I := make([]int, len(obuf)+1) 97 | V := make([]int, len(obuf)+1) 98 | 99 | for _, c := range obuf { 100 | buckets[c]++ 101 | } 102 | for i = 1; i < 256; i++ { 103 | buckets[i] += buckets[i-1] 104 | } 105 | copy(buckets[1:], buckets[:]) 106 | buckets[0] = 0 107 | 108 | for i, c := range obuf { 109 | buckets[c]++ 110 | I[buckets[c]] = i 111 | } 112 | 113 | I[0] = len(obuf) 114 | for i, c := range obuf { 115 | V[i] = buckets[c] 116 | } 117 | 118 | V[len(obuf)] = 0 119 | for i = 1; i < 256; i++ { 120 | if buckets[i] == buckets[i-1]+1 { 121 | I[buckets[i]] = -1 122 | } 123 | } 124 | I[0] = -1 125 | 126 | for h = 1; I[0] != -(len(obuf) + 1); h += h { 127 | var n int 128 | for i = 0; i < len(obuf)+1; { 129 | if I[i] < 0 { 130 | n -= I[i] 131 | i -= I[i] 132 | } else { 133 | if n != 0 { 134 | I[i-n] = -n 135 | } 136 | n = V[I[i]] + 1 - i 137 | split(I, V, i, n, h) 138 | i += n 139 | n = 0 140 | } 141 | } 142 | if n != 0 { 143 | I[i-n] = -n 144 | } 145 | } 146 | 147 | for i = 0; i < len(obuf)+1; i++ { 148 | I[V[i]] = i 149 | } 150 | return I 151 | } 152 | 153 | func matchlen(a, b []byte) (i int) { 154 | for i < len(a) && i < len(b) && a[i] == b[i] { 155 | i++ 156 | } 157 | return i 158 | } 159 | 160 | func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { 161 | if en-st < 2 { 162 | x := matchlen(obuf[I[st]:], nbuf) 163 | y := matchlen(obuf[I[en]:], nbuf) 164 | 165 | if x > y { 166 | return I[st], x 167 | } else { 168 | return I[en], y 169 | } 170 | } 171 | 172 | x := st + (en-st)/2 173 | if bytes.Compare(obuf[I[x]:], nbuf) < 0 { 174 | return search(I, obuf, nbuf, x, en) 175 | } else { 176 | return search(I, obuf, nbuf, st, x) 177 | } 178 | panic("unreached") 179 | } 180 | 181 | // Diff computes the difference between old and new, according to the bsdiff 182 | // algorithm, and writes the result to patch. 183 | func Diff(old, new io.Reader, patch io.Writer) error { 184 | obuf, err := ioutil.ReadAll(old) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | nbuf, err := ioutil.ReadAll(new) 190 | if err != nil { 191 | return err 192 | } 193 | 194 | pbuf, err := diffBytes(obuf, nbuf) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | _, err = patch.Write(pbuf) 200 | return err 201 | } 202 | 203 | func diffBytes(obuf, nbuf []byte) ([]byte, error) { 204 | var patch seekBuffer 205 | err := diff(obuf, nbuf, &patch) 206 | if err != nil { 207 | return nil, err 208 | } 209 | return patch.buf, nil 210 | } 211 | 212 | func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { 213 | var lenf int 214 | I := qsufsort(obuf) 215 | db := make([]byte, len(nbuf)) 216 | eb := make([]byte, len(nbuf)) 217 | var dblen, eblen int 218 | 219 | var hdr header 220 | hdr.Magic = magic 221 | hdr.NewSize = int64(len(nbuf)) 222 | err := binary.Write(patch, signMagLittleEndian{}, &hdr) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | // Compute the differences, writing ctrl as we go 228 | pfbz2, err := newBzip2Writer(patch) 229 | if err != nil { 230 | return err 231 | } 232 | var scan, pos, length int 233 | var lastscan, lastpos, lastoffset int 234 | for scan < len(nbuf) { 235 | var oldscore int 236 | scan += length 237 | for scsc := scan; scan < len(nbuf); scan++ { 238 | pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) 239 | 240 | for ; scsc < scan+length; scsc++ { 241 | if scsc+lastoffset < len(obuf) && 242 | obuf[scsc+lastoffset] == nbuf[scsc] { 243 | oldscore++ 244 | } 245 | } 246 | 247 | if (length == oldscore && length != 0) || length > oldscore+8 { 248 | break 249 | } 250 | 251 | if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { 252 | oldscore-- 253 | } 254 | } 255 | 256 | if length != oldscore || scan == len(nbuf) { 257 | var s, Sf int 258 | lenf = 0 259 | for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { 260 | if obuf[lastpos+i] == nbuf[lastscan+i] { 261 | s++ 262 | } 263 | i++ 264 | if s*2-i > Sf*2-lenf { 265 | Sf = s 266 | lenf = i 267 | } 268 | } 269 | 270 | lenb := 0 271 | if scan < len(nbuf) { 272 | var s, Sb int 273 | for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { 274 | if obuf[pos-i] == nbuf[scan-i] { 275 | s++ 276 | } 277 | if s*2-i > Sb*2-lenb { 278 | Sb = s 279 | lenb = i 280 | } 281 | } 282 | } 283 | 284 | if lastscan+lenf > scan-lenb { 285 | overlap := (lastscan + lenf) - (scan - lenb) 286 | s := 0 287 | Ss := 0 288 | lens := 0 289 | for i := 0; i < overlap; i++ { 290 | if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { 291 | s++ 292 | } 293 | if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { 294 | s-- 295 | } 296 | if s > Ss { 297 | Ss = s 298 | lens = i + 1 299 | } 300 | } 301 | 302 | lenf += lens - overlap 303 | lenb -= lens 304 | } 305 | 306 | for i := 0; i < lenf; i++ { 307 | db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] 308 | } 309 | for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { 310 | eb[eblen+i] = nbuf[lastscan+lenf+i] 311 | } 312 | 313 | dblen += lenf 314 | eblen += (scan - lenb) - (lastscan + lenf) 315 | 316 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) 317 | if err != nil { 318 | pfbz2.Close() 319 | return err 320 | } 321 | 322 | val := (scan - lenb) - (lastscan + lenf) 323 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) 324 | if err != nil { 325 | pfbz2.Close() 326 | return err 327 | } 328 | 329 | val = (pos - lenb) - (lastpos + lenf) 330 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) 331 | if err != nil { 332 | pfbz2.Close() 333 | return err 334 | } 335 | 336 | lastscan = scan - lenb 337 | lastpos = pos - lenb 338 | lastoffset = pos - scan 339 | } 340 | } 341 | err = pfbz2.Close() 342 | if err != nil { 343 | return err 344 | } 345 | 346 | // Compute size of compressed ctrl data 347 | l64, err := patch.Seek(0, 1) 348 | if err != nil { 349 | return err 350 | } 351 | hdr.CtrlLen = int64(l64 - 32) 352 | 353 | // Write compressed diff data 354 | pfbz2, err = newBzip2Writer(patch) 355 | if err != nil { 356 | return err 357 | } 358 | n, err := pfbz2.Write(db[:dblen]) 359 | if err != nil { 360 | pfbz2.Close() 361 | return err 362 | } 363 | if n != dblen { 364 | pfbz2.Close() 365 | return io.ErrShortWrite 366 | } 367 | err = pfbz2.Close() 368 | if err != nil { 369 | return err 370 | } 371 | 372 | // Compute size of compressed diff data 373 | n64, err := patch.Seek(0, 1) 374 | if err != nil { 375 | return err 376 | } 377 | hdr.DiffLen = n64 - l64 378 | 379 | // Write compressed extra data 380 | pfbz2, err = newBzip2Writer(patch) 381 | if err != nil { 382 | return err 383 | } 384 | n, err = pfbz2.Write(eb[:eblen]) 385 | if err != nil { 386 | pfbz2.Close() 387 | return err 388 | } 389 | if n != eblen { 390 | pfbz2.Close() 391 | return io.ErrShortWrite 392 | } 393 | err = pfbz2.Close() 394 | if err != nil { 395 | return err 396 | } 397 | 398 | // Seek to the beginning, write the header, and close the file 399 | _, err = patch.Seek(0, 0) 400 | if err != nil { 401 | return err 402 | } 403 | err = binary.Write(patch, signMagLittleEndian{}, &hdr) 404 | if err != nil { 405 | return err 406 | } 407 | return nil 408 | } 409 | -------------------------------------------------------------------------------- /internal/binarydist/diff_test.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "testing" 9 | ) 10 | 11 | var diffT = []struct { 12 | old *os.File 13 | new *os.File 14 | }{ 15 | { 16 | old: mustWriteRandFile("test.old", 1e3), 17 | new: mustWriteRandFile("test.new", 1e3), 18 | }, 19 | { 20 | old: mustOpen("testdata/sample.old"), 21 | new: mustOpen("testdata/sample.new"), 22 | }, 23 | } 24 | 25 | func TestDiff(t *testing.T) { 26 | for _, s := range diffT { 27 | got, err := ioutil.TempFile("/tmp", "bspatch.") 28 | if err != nil { 29 | panic(err) 30 | } 31 | os.Remove(got.Name()) 32 | 33 | exp, err := ioutil.TempFile("/tmp", "bspatch.") 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name()) 39 | cmd.Stdout = os.Stdout 40 | err = cmd.Run() 41 | os.Remove(exp.Name()) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | err = Diff(s.old, s.new, got) 47 | if err != nil { 48 | t.Fatal("err", err) 49 | } 50 | 51 | _, err = got.Seek(0, 0) 52 | if err != nil { 53 | panic(err) 54 | } 55 | gotBuf := mustReadAll(got) 56 | expBuf := mustReadAll(exp) 57 | 58 | if !bytes.Equal(gotBuf, expBuf) { 59 | t.Fail() 60 | t.Logf("diff %s %s", s.old.Name(), s.new.Name()) 61 | t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf)) 62 | t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf)) 63 | i := matchlen(gotBuf, expBuf) 64 | t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i]) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/binarydist/doc.go: -------------------------------------------------------------------------------- 1 | // Package binarydist implements binary diff and patch as described on 2 | // http://www.daemonology.net/bsdiff/. It reads and writes files 3 | // compatible with the tools there. 4 | package binarydist 5 | 6 | var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} 7 | 8 | // File format: 9 | // 0 8 "BSDIFF40" 10 | // 8 8 X 11 | // 16 8 Y 12 | // 24 8 sizeof(newfile) 13 | // 32 X bzip2(control block) 14 | // 32+X Y bzip2(diff block) 15 | // 32+X+Y ??? bzip2(extra block) 16 | // with control block a set of triples (x,y,z) meaning "add x bytes 17 | // from oldfile to x bytes from the diff block; copy y bytes from the 18 | // extra block; seek forwards in oldfile by z bytes". 19 | type header struct { 20 | Magic [8]byte 21 | CtrlLen int64 22 | DiffLen int64 23 | NewSize int64 24 | } 25 | -------------------------------------------------------------------------------- /internal/binarydist/encoding.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | // SignMagLittleEndian is the numeric encoding used by the bsdiff tools. 4 | // It implements binary.ByteOrder using a sign-magnitude format 5 | // and little-endian byte order. Only methods Uint64 and String 6 | // have been written; the rest panic. 7 | type signMagLittleEndian struct{} 8 | 9 | func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } 10 | 11 | func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } 12 | 13 | func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } 14 | 15 | func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } 16 | 17 | func (signMagLittleEndian) Uint64(b []byte) uint64 { 18 | y := int64(b[0]) | 19 | int64(b[1])<<8 | 20 | int64(b[2])<<16 | 21 | int64(b[3])<<24 | 22 | int64(b[4])<<32 | 23 | int64(b[5])<<40 | 24 | int64(b[6])<<48 | 25 | int64(b[7]&0x7f)<<56 26 | 27 | if b[7]&0x80 != 0 { 28 | y = -y 29 | } 30 | return uint64(y) 31 | } 32 | 33 | func (signMagLittleEndian) PutUint64(b []byte, v uint64) { 34 | x := int64(v) 35 | neg := x < 0 36 | if neg { 37 | x = -x 38 | } 39 | 40 | b[0] = byte(x) 41 | b[1] = byte(x >> 8) 42 | b[2] = byte(x >> 16) 43 | b[3] = byte(x >> 24) 44 | b[4] = byte(x >> 32) 45 | b[5] = byte(x >> 40) 46 | b[6] = byte(x >> 48) 47 | b[7] = byte(x >> 56) 48 | if neg { 49 | b[7] |= 0x80 50 | } 51 | } 52 | 53 | func (signMagLittleEndian) String() string { return "signMagLittleEndian" } 54 | -------------------------------------------------------------------------------- /internal/binarydist/patch.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "bytes" 5 | "compress/bzip2" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "io/ioutil" 10 | ) 11 | 12 | var ErrCorrupt = errors.New("corrupt patch") 13 | 14 | // Patch applies patch to old, according to the bspatch algorithm, 15 | // and writes the result to new. 16 | func Patch(old io.Reader, new io.Writer, patch io.Reader) error { 17 | var hdr header 18 | err := binary.Read(patch, signMagLittleEndian{}, &hdr) 19 | if err != nil { 20 | return err 21 | } 22 | if hdr.Magic != magic { 23 | return ErrCorrupt 24 | } 25 | if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { 26 | return ErrCorrupt 27 | } 28 | 29 | ctrlbuf := make([]byte, hdr.CtrlLen) 30 | _, err = io.ReadFull(patch, ctrlbuf) 31 | if err != nil { 32 | return err 33 | } 34 | cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) 35 | 36 | diffbuf := make([]byte, hdr.DiffLen) 37 | _, err = io.ReadFull(patch, diffbuf) 38 | if err != nil { 39 | return err 40 | } 41 | dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) 42 | 43 | // The entire rest of the file is the extra block. 44 | epfbz2 := bzip2.NewReader(patch) 45 | 46 | obuf, err := ioutil.ReadAll(old) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | nbuf := make([]byte, hdr.NewSize) 52 | 53 | var oldpos, newpos int64 54 | for newpos < hdr.NewSize { 55 | var ctrl struct{ Add, Copy, Seek int64 } 56 | err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // Sanity-check 62 | if newpos+ctrl.Add > hdr.NewSize { 63 | return ErrCorrupt 64 | } 65 | 66 | // Read diff string 67 | _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) 68 | if err != nil { 69 | return ErrCorrupt 70 | } 71 | 72 | // Add old data to diff string 73 | for i := int64(0); i < ctrl.Add; i++ { 74 | if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { 75 | nbuf[newpos+i] += obuf[oldpos+i] 76 | } 77 | } 78 | 79 | // Adjust pointers 80 | newpos += ctrl.Add 81 | oldpos += ctrl.Add 82 | 83 | // Sanity-check 84 | if newpos+ctrl.Copy > hdr.NewSize { 85 | return ErrCorrupt 86 | } 87 | 88 | // Read extra string 89 | _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) 90 | if err != nil { 91 | return ErrCorrupt 92 | } 93 | 94 | // Adjust pointers 95 | newpos += ctrl.Copy 96 | oldpos += ctrl.Seek 97 | } 98 | 99 | // Write the new file 100 | for len(nbuf) > 0 { 101 | n, err := new.Write(nbuf) 102 | if err != nil { 103 | return err 104 | } 105 | nbuf = nbuf[n:] 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /internal/binarydist/patch_test.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | func TestPatch(t *testing.T) { 11 | mustWriteRandFile("test.old", 1e3) 12 | mustWriteRandFile("test.new", 1e3) 13 | 14 | got, err := ioutil.TempFile("/tmp", "bspatch.") 15 | if err != nil { 16 | panic(err) 17 | } 18 | os.Remove(got.Name()) 19 | 20 | err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run() 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | err = Patch(mustOpen("test.old"), got, mustOpen("test.patch")) 26 | if err != nil { 27 | t.Fatal("err", err) 28 | } 29 | 30 | ref, err := got.Seek(0, 2) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | t.Logf("got %d bytes", ref) 36 | if n := fileCmp(got, mustOpen("test.new")); n > -1 { 37 | t.Fatalf("produced different output at pos %d", n) 38 | } 39 | } 40 | 41 | func TestPatchHk(t *testing.T) { 42 | got, err := ioutil.TempFile("/tmp", "bspatch.") 43 | if err != nil { 44 | panic(err) 45 | } 46 | os.Remove(got.Name()) 47 | 48 | err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch")) 49 | if err != nil { 50 | t.Fatal("err", err) 51 | } 52 | 53 | ref, err := got.Seek(0, 2) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | t.Logf("got %d bytes", ref) 59 | if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 { 60 | t.Fatalf("produced different output at pos %d", n) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /internal/binarydist/seek.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type seekBuffer struct { 8 | buf []byte 9 | pos int 10 | } 11 | 12 | func (b *seekBuffer) Write(p []byte) (n int, err error) { 13 | n = copy(b.buf[b.pos:], p) 14 | if n == len(p) { 15 | b.pos += n 16 | return n, nil 17 | } 18 | b.buf = append(b.buf, p[n:]...) 19 | b.pos += len(p) 20 | return len(p), nil 21 | } 22 | 23 | func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { 24 | var abs int64 25 | switch whence { 26 | case 0: 27 | abs = offset 28 | case 1: 29 | abs = int64(b.pos) + offset 30 | case 2: 31 | abs = int64(len(b.buf)) + offset 32 | default: 33 | return 0, errors.New("binarydist: invalid whence") 34 | } 35 | if abs < 0 { 36 | return 0, errors.New("binarydist: negative position") 37 | } 38 | if abs >= 1<<31 { 39 | return 0, errors.New("binarydist: position out of range") 40 | } 41 | b.pos = int(abs) 42 | return abs, nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/binarydist/sort_test.go: -------------------------------------------------------------------------------- 1 | package binarydist 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | ) 8 | 9 | var sortT = [][]byte{ 10 | mustRandBytes(1000), 11 | mustReadAll(mustOpen("test.old")), 12 | []byte("abcdefabcdef"), 13 | } 14 | 15 | func TestQsufsort(t *testing.T) { 16 | for _, s := range sortT { 17 | I := qsufsort(s) 18 | for i := 1; i < len(I); i++ { 19 | if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 { 20 | t.Fatalf("unsorted at %d", i) 21 | } 22 | } 23 | } 24 | } 25 | 26 | func mustRandBytes(n int) []byte { 27 | b := make([]byte, n) 28 | _, err := rand.Read(b) 29 | if err != nil { 30 | panic(err) 31 | } 32 | return b 33 | } 34 | -------------------------------------------------------------------------------- /internal/binarydist/testdata/sample.new: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.new -------------------------------------------------------------------------------- /internal/binarydist/testdata/sample.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.old -------------------------------------------------------------------------------- /internal/binarydist/testdata/sample.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.patch -------------------------------------------------------------------------------- /internal/osext/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /internal/osext/README.md: -------------------------------------------------------------------------------- 1 | ### Extensions to the "os" package. 2 | 3 | ## Find the current Executable and ExecutableFolder. 4 | 5 | There is sometimes utility in finding the current executable file 6 | that is running. This can be used for upgrading the current executable 7 | or finding resources located relative to the executable file. Both 8 | working directory and the os.Args[0] value are arbitrary and cannot 9 | be relied on; os.Args[0] can be "faked". 10 | 11 | Multi-platform and supports: 12 | * Linux 13 | * OS X 14 | * Windows 15 | * Plan 9 16 | * BSDs. 17 | -------------------------------------------------------------------------------- /internal/osext/osext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Extensions to the standard "os" package. 6 | package osext 7 | 8 | import "path/filepath" 9 | 10 | // Executable returns an absolute path that can be used to 11 | // re-invoke the current program. 12 | // It may not be valid after the current program exits. 13 | func Executable() (string, error) { 14 | p, err := executable() 15 | return filepath.Clean(p), err 16 | } 17 | 18 | // Returns same path as Executable, returns just the folder 19 | // path. Excludes the executable name and any trailing slash. 20 | func ExecutableFolder() (string, error) { 21 | p, err := Executable() 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | return filepath.Dir(p), nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/osext/osext_plan9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package osext 6 | 7 | import ( 8 | "os" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | func executable() (string, error) { 14 | f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") 15 | if err != nil { 16 | return "", err 17 | } 18 | defer f.Close() 19 | return syscall.Fd2path(int(f.Fd())) 20 | } 21 | -------------------------------------------------------------------------------- /internal/osext/osext_procfs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux netbsd openbsd solaris dragonfly 6 | 7 | package osext 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "os" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | func executable() (string, error) { 18 | switch runtime.GOOS { 19 | case "linux": 20 | const deletedTag = " (deleted)" 21 | execpath, err := os.Readlink("/proc/self/exe") 22 | if err != nil { 23 | return execpath, err 24 | } 25 | execpath = strings.TrimSuffix(execpath, deletedTag) 26 | execpath = strings.TrimPrefix(execpath, deletedTag) 27 | return execpath, nil 28 | case "netbsd": 29 | return os.Readlink("/proc/curproc/exe") 30 | case "openbsd", "dragonfly": 31 | return os.Readlink("/proc/curproc/file") 32 | case "solaris": 33 | return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) 34 | } 35 | return "", errors.New("ExecPath not implemented for " + runtime.GOOS) 36 | } 37 | -------------------------------------------------------------------------------- /internal/osext/osext_sysctl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin freebsd 6 | 7 | package osext 8 | 9 | import ( 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "syscall" 14 | "unsafe" 15 | ) 16 | 17 | var initCwd, initCwdErr = os.Getwd() 18 | 19 | func executable() (string, error) { 20 | var mib [4]int32 21 | switch runtime.GOOS { 22 | case "freebsd": 23 | mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} 24 | case "darwin": 25 | mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} 26 | } 27 | 28 | n := uintptr(0) 29 | // Get length. 30 | _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) 31 | if errNum != 0 { 32 | return "", errNum 33 | } 34 | if n == 0 { // This shouldn't happen. 35 | return "", nil 36 | } 37 | buf := make([]byte, n) 38 | _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) 39 | if errNum != 0 { 40 | return "", errNum 41 | } 42 | if n == 0 { // This shouldn't happen. 43 | return "", nil 44 | } 45 | for i, v := range buf { 46 | if v == 0 { 47 | buf = buf[:i] 48 | break 49 | } 50 | } 51 | var err error 52 | execPath := string(buf) 53 | // execPath will not be empty due to above checks. 54 | // Try to get the absolute path if the execPath is not rooted. 55 | if execPath[0] != '/' { 56 | execPath, err = getAbs(execPath) 57 | if err != nil { 58 | return execPath, err 59 | } 60 | } 61 | // For darwin KERN_PROCARGS may return the path to a symlink rather than the 62 | // actual executable. 63 | if runtime.GOOS == "darwin" { 64 | if execPath, err = filepath.EvalSymlinks(execPath); err != nil { 65 | return execPath, err 66 | } 67 | } 68 | return execPath, nil 69 | } 70 | 71 | func getAbs(execPath string) (string, error) { 72 | if initCwdErr != nil { 73 | return execPath, initCwdErr 74 | } 75 | // The execPath may begin with a "../" or a "./" so clean it first. 76 | // Join the two paths, trailing and starting slashes undetermined, so use 77 | // the generic Join function. 78 | return filepath.Join(initCwd, filepath.Clean(execPath)), nil 79 | } 80 | -------------------------------------------------------------------------------- /internal/osext/osext_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin linux freebsd netbsd windows 6 | 7 | package osext 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "io" 13 | "os" 14 | "os/exec" 15 | "path/filepath" 16 | "runtime" 17 | "testing" 18 | ) 19 | 20 | const ( 21 | executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE" 22 | 23 | executableEnvValueMatch = "match" 24 | executableEnvValueDelete = "delete" 25 | ) 26 | 27 | func TestPrintExecutable(t *testing.T) { 28 | ef, err := Executable() 29 | if err != nil { 30 | t.Fatalf("Executable failed: %v", err) 31 | } 32 | t.Log("Executable:", ef) 33 | } 34 | func TestPrintExecutableFolder(t *testing.T) { 35 | ef, err := ExecutableFolder() 36 | if err != nil { 37 | t.Fatalf("ExecutableFolder failed: %v", err) 38 | } 39 | t.Log("Executable Folder:", ef) 40 | } 41 | func TestExecutableFolder(t *testing.T) { 42 | ef, err := ExecutableFolder() 43 | if err != nil { 44 | t.Fatalf("ExecutableFolder failed: %v", err) 45 | } 46 | if ef[len(ef)-1] == filepath.Separator { 47 | t.Fatal("ExecutableFolder ends with a trailing slash.") 48 | } 49 | } 50 | func TestExecutableMatch(t *testing.T) { 51 | ep, err := Executable() 52 | if err != nil { 53 | t.Fatalf("Executable failed: %v", err) 54 | } 55 | 56 | // fullpath to be of the form "dir/prog". 57 | dir := filepath.Dir(filepath.Dir(ep)) 58 | fullpath, err := filepath.Rel(dir, ep) 59 | if err != nil { 60 | t.Fatalf("filepath.Rel: %v", err) 61 | } 62 | // Make child start with a relative program path. 63 | // Alter argv[0] for child to verify getting real path without argv[0]. 64 | cmd := &exec.Cmd{ 65 | Dir: dir, 66 | Path: fullpath, 67 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)}, 68 | } 69 | out, err := cmd.CombinedOutput() 70 | if err != nil { 71 | t.Fatalf("exec(self) failed: %v", err) 72 | } 73 | outs := string(out) 74 | if !filepath.IsAbs(outs) { 75 | t.Fatalf("Child returned %q, want an absolute path", out) 76 | } 77 | if !sameFile(outs, ep) { 78 | t.Fatalf("Child returned %q, not the same file as %q", out, ep) 79 | } 80 | } 81 | 82 | func TestExecutableDelete(t *testing.T) { 83 | if runtime.GOOS != "linux" { 84 | t.Skip() 85 | } 86 | fpath, err := Executable() 87 | if err != nil { 88 | t.Fatalf("Executable failed: %v", err) 89 | } 90 | 91 | r, w := io.Pipe() 92 | stderrBuff := &bytes.Buffer{} 93 | stdoutBuff := &bytes.Buffer{} 94 | cmd := &exec.Cmd{ 95 | Path: fpath, 96 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)}, 97 | Stdin: r, 98 | Stderr: stderrBuff, 99 | Stdout: stdoutBuff, 100 | } 101 | err = cmd.Start() 102 | if err != nil { 103 | t.Fatalf("exec(self) start failed: %v", err) 104 | } 105 | 106 | tempPath := fpath + "_copy" 107 | _ = os.Remove(tempPath) 108 | 109 | err = copyFile(tempPath, fpath) 110 | if err != nil { 111 | t.Fatalf("copy file failed: %v", err) 112 | } 113 | err = os.Remove(fpath) 114 | if err != nil { 115 | t.Fatalf("remove running test file failed: %v", err) 116 | } 117 | err = os.Rename(tempPath, fpath) 118 | if err != nil { 119 | t.Fatalf("rename copy to previous name failed: %v", err) 120 | } 121 | 122 | w.Write([]byte{0}) 123 | w.Close() 124 | 125 | err = cmd.Wait() 126 | if err != nil { 127 | t.Fatalf("exec wait failed: %v", err) 128 | } 129 | 130 | childPath := stderrBuff.String() 131 | if !filepath.IsAbs(childPath) { 132 | t.Fatalf("Child returned %q, want an absolute path", childPath) 133 | } 134 | if !sameFile(childPath, fpath) { 135 | t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath) 136 | } 137 | } 138 | 139 | func sameFile(fn1, fn2 string) bool { 140 | fi1, err := os.Stat(fn1) 141 | if err != nil { 142 | return false 143 | } 144 | fi2, err := os.Stat(fn2) 145 | if err != nil { 146 | return false 147 | } 148 | return os.SameFile(fi1, fi2) 149 | } 150 | func copyFile(dest, src string) error { 151 | df, err := os.Create(dest) 152 | if err != nil { 153 | return err 154 | } 155 | defer df.Close() 156 | 157 | sf, err := os.Open(src) 158 | if err != nil { 159 | return err 160 | } 161 | defer sf.Close() 162 | 163 | _, err = io.Copy(df, sf) 164 | return err 165 | } 166 | 167 | func TestMain(m *testing.M) { 168 | env := os.Getenv(executableEnvVar) 169 | switch env { 170 | case "": 171 | os.Exit(m.Run()) 172 | case executableEnvValueMatch: 173 | // First chdir to another path. 174 | dir := "/" 175 | if runtime.GOOS == "windows" { 176 | dir = filepath.VolumeName(".") 177 | } 178 | os.Chdir(dir) 179 | if ep, err := Executable(); err != nil { 180 | fmt.Fprint(os.Stderr, "ERROR: ", err) 181 | } else { 182 | fmt.Fprint(os.Stderr, ep) 183 | } 184 | case executableEnvValueDelete: 185 | bb := make([]byte, 1) 186 | var err error 187 | n, err := os.Stdin.Read(bb) 188 | if err != nil { 189 | fmt.Fprint(os.Stderr, "ERROR: ", err) 190 | os.Exit(2) 191 | } 192 | if n != 1 { 193 | fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n) 194 | os.Exit(2) 195 | } 196 | if ep, err := Executable(); err != nil { 197 | fmt.Fprint(os.Stderr, "ERROR: ", err) 198 | } else { 199 | fmt.Fprint(os.Stderr, ep) 200 | } 201 | } 202 | os.Exit(0) 203 | } 204 | -------------------------------------------------------------------------------- /internal/osext/osext_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package osext 6 | 7 | import ( 8 | "syscall" 9 | "unicode/utf16" 10 | "unsafe" 11 | ) 12 | 13 | var ( 14 | kernel = syscall.MustLoadDLL("kernel32.dll") 15 | getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") 16 | ) 17 | 18 | // GetModuleFileName() with hModule = NULL 19 | func executable() (exePath string, err error) { 20 | return getModuleFileName() 21 | } 22 | 23 | func getModuleFileName() (string, error) { 24 | var n uint32 25 | b := make([]uint16, syscall.MAX_PATH) 26 | size := uint32(len(b)) 27 | 28 | r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) 29 | n = uint32(r0) 30 | if n == 0 { 31 | return "", e1 32 | } 33 | return string(utf16.Decode(b[0:n])), nil 34 | } 35 | -------------------------------------------------------------------------------- /patcher.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/inconshreveable/go-update/internal/binarydist" 7 | ) 8 | 9 | // Patcher defines an interface for applying binary patches to an old item to get an updated item. 10 | type Patcher interface { 11 | Patch(old io.Reader, new io.Writer, patch io.Reader) error 12 | } 13 | 14 | type patchFn func(io.Reader, io.Writer, io.Reader) error 15 | 16 | func (fn patchFn) Patch(old io.Reader, new io.Writer, patch io.Reader) error { 17 | return fn(old, new, patch) 18 | } 19 | 20 | // NewBSDifferPatcher returns a new Patcher that applies binary patches using 21 | // the bsdiff algorithm. See http://www.daemonology.net/bsdiff/ 22 | func NewBSDiffPatcher() Patcher { 23 | return patchFn(binarydist.Patch) 24 | } 25 | -------------------------------------------------------------------------------- /verifier.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "encoding/asn1" 9 | "errors" 10 | "math/big" 11 | ) 12 | 13 | // Verifier defines an interface for verfiying an update's signature with a public key. 14 | type Verifier interface { 15 | VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error 16 | } 17 | 18 | type verifyFn func([]byte, []byte, crypto.Hash, crypto.PublicKey) error 19 | 20 | func (fn verifyFn) VerifySignature(checksum []byte, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { 21 | return fn(checksum, signature, hash, publicKey) 22 | } 23 | 24 | // NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates. 25 | func NewRSAVerifier() Verifier { 26 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { 27 | key, ok := publicKey.(*rsa.PublicKey) 28 | if !ok { 29 | return errors.New("not a valid RSA public key") 30 | } 31 | return rsa.VerifyPKCS1v15(key, hash, checksum, signature) 32 | }) 33 | } 34 | 35 | type rsDER struct { 36 | R *big.Int 37 | S *big.Int 38 | } 39 | 40 | // NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates. 41 | func NewECDSAVerifier() Verifier { 42 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { 43 | key, ok := publicKey.(*ecdsa.PublicKey) 44 | if !ok { 45 | return errors.New("not a valid ECDSA public key") 46 | } 47 | var rs rsDER 48 | if _, err := asn1.Unmarshal(signature, &rs); err != nil { 49 | return err 50 | } 51 | if !ecdsa.Verify(key, checksum, rs.R, rs.S) { 52 | return errors.New("failed to verify ecsda signature") 53 | } 54 | return nil 55 | }) 56 | } 57 | 58 | // NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates. 59 | func NewDSAVerifier() Verifier { 60 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { 61 | key, ok := publicKey.(*dsa.PublicKey) 62 | if !ok { 63 | return errors.New("not a valid DSA public key") 64 | } 65 | var rs rsDER 66 | if _, err := asn1.Unmarshal(signature, &rs); err != nil { 67 | return err 68 | } 69 | if !dsa.Verify(key, checksum, rs.R, rs.S) { 70 | return errors.New("failed to verify ecsda signature") 71 | } 72 | return nil 73 | }) 74 | } 75 | --------------------------------------------------------------------------------