├── .github └── workflows │ └── vulncheck.yml ├── .gitignore ├── LICENSE ├── LICENSE.minisig ├── NOTICE ├── README.md ├── apply.go ├── apply_test.go ├── doc.go ├── go.mod ├── go.sum ├── hide_noop.go ├── hide_windows.go ├── internal ├── binarydist │ ├── .gitignore │ ├── 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 ├── minisign.go ├── minisign_test.go ├── patcher.go ├── test.key └── test.pub /.github/workflows/vulncheck.yml: -------------------------------------------------------------------------------- 1 | name: VulnCheck 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - main 7 | push: 8 | branches: 9 | - master 10 | - main 11 | jobs: 12 | vulncheck: 13 | name: Analysis 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | go-version: [ 1.19 ] 18 | steps: 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@v3 21 | - uses: actions/setup-go@v3 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | check-latest: true 25 | - name: Get govulncheck 26 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 27 | shell: bash 28 | - name: Run govulncheck 29 | run: govulncheck ./... 30 | shell: bash 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE.minisig: -------------------------------------------------------------------------------- 1 | untrusted comment: signature from minisign secret key 2 | RUQhjNB8gjlNDZN66rN1aESIzZK6jG17OXx2wki+TYYuhwlW9cOq0qIHtTEt4b776mziUbtITtm1+UrwfODM32VR3jG2eqn/NwA= 3 | trusted comment: timestamp:1639597543 file:LICENSE hashed 4 | rbQFZEBnFNdFMLj+6bhp2ADasgXnPEkpDbpytMKcxbCa+wm0UFUB1nputqIANfpc6GTRq4JPa0N97y/uzrRuBQ== 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 MinIO,Inc rewrites and modifications 2 | Copyright 2015 Alan Shreve 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![API Reference](https://img.shields.io/badge/api-reference-blue.svg)](https://pkg.go.dev/github.com/minio/selfupdate?tab=doc) [![Apache V2 License](https://img.shields.io/badge/license-Apache%20V2-blue.svg)](https://github.com/minio/selfupdate/blob/master/LICENSE) 2 | 3 | # selfupdate: Build self-updating Go programs 4 | 5 | > NOTE: Original work at github.com/inconshreveable/go-update, modified for the needs within MinIO project 6 | 7 | Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets) 8 | A program can update itself by replacing its executable file with a new version. 9 | 10 | It provides the flexibility to implement different updating user experiences 11 | like auto-updating, or manual user-initiated updates. It also boasts 12 | advanced features like binary patching and code signing verification. 13 | 14 | Example of updating from a URL: 15 | 16 | ```go 17 | import ( 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/minio/selfupdate" 22 | ) 23 | 24 | func doUpdate(url string) error { 25 | resp, err := http.Get(url) 26 | if err != nil { 27 | return err 28 | } 29 | defer resp.Body.Close() 30 | err = selfupdate.Apply(resp.Body, selfupdate.Options{}) 31 | if err != nil { 32 | // error handling 33 | } 34 | return err 35 | } 36 | ``` 37 | 38 | ## Features 39 | 40 | - Cross platform support (Windows too!) 41 | - Binary patch application 42 | - Checksum verification 43 | - Code signing verification 44 | - Support for updating arbitrary files 45 | 46 | ## License 47 | This SDK is distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), see LICENSE for more information. Original work was also distributed under the same license. 48 | -------------------------------------------------------------------------------- /apply.go: -------------------------------------------------------------------------------- 1 | package selfupdate 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/minio/selfupdate/internal/osext" 14 | ) 15 | 16 | // Apply performs an update of the current executable or opts.TargetFile, with 17 | // the contents of the given io.Reader. When the update fails, it is unlikely 18 | // that old executable is corrupted, but still, applications need to check the 19 | // returned error with RollbackError() and notify the user of the bad news and 20 | // ask them to recover manually. 21 | func Apply(update io.Reader, opts Options) error { 22 | err := PrepareAndCheckBinary(update, opts) 23 | if err != nil { 24 | return err 25 | } 26 | return CommitBinary(opts) 27 | } 28 | 29 | // PrepareAndCheckBinary reads the new binary content from io.Reader and performs the following actions: 30 | // 1. If configured, applies the contents of the update io.Reader as a binary patch. 31 | // 2. If configured, computes the checksum of the executable and verifies it matches. 32 | // 3. If configured, verifies the signature with a public key. 33 | // 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file 34 | func PrepareAndCheckBinary(update io.Reader, opts Options) error { 35 | // get target path 36 | targetPath, err := opts.getPath() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | var newBytes []byte 42 | if opts.Patcher != nil { 43 | if newBytes, err = opts.applyPatch(update, targetPath); err != nil { 44 | return err 45 | } 46 | } else { 47 | // no patch to apply, go on through 48 | if newBytes, err = ioutil.ReadAll(update); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | // verify checksum if requested 54 | if opts.Checksum != nil { 55 | if err = opts.verifyChecksum(newBytes); err != nil { 56 | return err 57 | } 58 | } 59 | 60 | if opts.Verifier != nil { 61 | if err = opts.Verifier.Verify(newBytes); err != nil { 62 | return err 63 | } 64 | } 65 | 66 | // get the directory the executable exists in 67 | updateDir := filepath.Dir(targetPath) 68 | filename := filepath.Base(targetPath) 69 | 70 | // Copy the contents of newbinary to a new executable file 71 | newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) 72 | fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.getMode()) 73 | if err != nil { 74 | return err 75 | } 76 | defer fp.Close() 77 | 78 | _, err = io.Copy(fp, bytes.NewReader(newBytes)) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | // if we don't call fp.Close(), windows won't let us move the new executable 84 | // because the file will still be "in use" 85 | fp.Close() 86 | return nil 87 | } 88 | 89 | // CommitBinary moves the new executable to the location of the current executable or opts.TargetPath 90 | // if specified. It performs the following operations: 91 | // 1. Renames /path/to/target to /path/to/.target.old 92 | // 2. Renames /path/to/.target.new to /path/to/target 93 | // 3. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, 94 | // the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. 95 | // 4. If the final rename fails, attempts to roll back by renaming /path/to/.target.old 96 | // back to /path/to/target. 97 | // 98 | // If the roll back operation fails, the file system is left in an inconsistent state where there is 99 | // no new executable file and the old executable file could not be be moved to its original location. 100 | // In this case you should notify the user of the bad news and ask them to recover manually. Applications 101 | // can determine whether the rollback failed by calling RollbackError, see the documentation on that function 102 | // for additional detail. 103 | func CommitBinary(opts Options) error { 104 | // get the directory the file exists in 105 | targetPath, err := opts.getPath() 106 | if err != nil { 107 | return err 108 | } 109 | 110 | updateDir := filepath.Dir(targetPath) 111 | filename := filepath.Base(targetPath) 112 | newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) 113 | 114 | // this is where we'll move the executable to so that we can swap in the updated replacement 115 | oldPath := opts.OldSavePath 116 | removeOld := opts.OldSavePath == "" 117 | if removeOld { 118 | oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) 119 | } 120 | 121 | // delete any existing old exec file - this is necessary on Windows for two reasons: 122 | // 1. after a successful update, Windows can't remove the .old file because the process is still running 123 | // 2. windows rename operations fail if the destination file already exists 124 | _ = os.Remove(oldPath) 125 | 126 | // move the existing executable to a new file in the same directory 127 | err = os.Rename(targetPath, oldPath) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | // move the new exectuable in to become the new program 133 | err = os.Rename(newPath, targetPath) 134 | 135 | if err != nil { 136 | // move unsuccessful 137 | // 138 | // The filesystem is now in a bad state. We have successfully 139 | // moved the existing binary to a new location, but we couldn't move the new 140 | // binary to take its place. That means there is no file where the current executable binary 141 | // used to be! 142 | // Try to rollback by restoring the old binary to its original path. 143 | rerr := os.Rename(oldPath, targetPath) 144 | if rerr != nil { 145 | return &rollbackErr{err, rerr} 146 | } 147 | 148 | return err 149 | } 150 | 151 | // move successful, remove the old binary if needed 152 | if removeOld { 153 | errRemove := os.Remove(oldPath) 154 | 155 | // windows has trouble with removing old binaries, so hide it instead 156 | if errRemove != nil { 157 | _ = hideFile(oldPath) 158 | } 159 | } 160 | 161 | return nil 162 | } 163 | 164 | // RollbackError takes an error value returned by Apply and returns the error, if any, 165 | // that occurred when attempting to roll back from a failed update. Applications should 166 | // always call this function on any non-nil errors returned by Apply. 167 | // 168 | // If no rollback was needed or if the rollback was successful, RollbackError returns nil, 169 | // otherwise it returns the error encountered when trying to roll back. 170 | func RollbackError(err error) error { 171 | if err == nil { 172 | return nil 173 | } 174 | if rerr, ok := err.(*rollbackErr); ok { 175 | return rerr.rollbackErr 176 | } 177 | return nil 178 | } 179 | 180 | type rollbackErr struct { 181 | error // original error 182 | rollbackErr error // error encountered while rolling back 183 | } 184 | 185 | type Options struct { 186 | // TargetPath defines the path to the file to update. 187 | // The emptry string means 'the executable file of the running program'. 188 | TargetPath string 189 | 190 | // Create TargetPath replacement with this file mode. If zero, defaults to 0755. 191 | TargetMode os.FileMode 192 | 193 | // Checksum of the new binary to verify against. If nil, no checksum or signature verification is done. 194 | Checksum []byte 195 | 196 | // Verifier for signature verification. If nil, no signature verification is done. 197 | Verifier *Verifier 198 | 199 | // Use this hash function to generate the checksum. If not set, SHA256 is used. 200 | Hash crypto.Hash 201 | 202 | // If nil, treat the update as a complete replacement for the contents of the file at TargetPath. 203 | // If non-nil, treat the update contents as a patch and use this object to apply the patch. 204 | Patcher Patcher 205 | 206 | // Store the old executable file at this path after a successful update. 207 | // The empty string means the old executable file will be removed after the update. 208 | OldSavePath string 209 | } 210 | 211 | // CheckPermissions determines whether the process has the correct permissions to 212 | // perform the requested update. If the update can proceed, it returns nil, otherwise 213 | // it returns the error that would occur if an update were attempted. 214 | func (o *Options) CheckPermissions() error { 215 | // get the directory the file exists in 216 | path, err := o.getPath() 217 | if err != nil { 218 | return err 219 | } 220 | 221 | fileDir := filepath.Dir(path) 222 | fileName := filepath.Base(path) 223 | 224 | // attempt to open a file in the file's directory 225 | newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.check-perm", fileName)) 226 | fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.getMode()) 227 | if err != nil { 228 | return err 229 | } 230 | fp.Close() 231 | 232 | _ = os.Remove(newPath) 233 | return nil 234 | } 235 | 236 | func (o *Options) getPath() (string, error) { 237 | if o.TargetPath == "" { 238 | return osext.Executable() 239 | } else { 240 | return o.TargetPath, nil 241 | } 242 | } 243 | 244 | func (o *Options) getMode() os.FileMode { 245 | if o.TargetMode == 0 { 246 | return 0755 247 | } 248 | return o.TargetMode 249 | } 250 | 251 | func (o *Options) getHash() crypto.Hash { 252 | if o.Hash == 0 { 253 | o.Hash = crypto.SHA256 254 | } 255 | return o.Hash 256 | } 257 | 258 | func (o *Options) applyPatch(patch io.Reader, targetPath string) ([]byte, error) { 259 | // open the file to patch 260 | old, err := os.Open(targetPath) 261 | if err != nil { 262 | return nil, err 263 | } 264 | defer old.Close() 265 | 266 | // apply the patch 267 | var applied bytes.Buffer 268 | if err = o.Patcher.Patch(old, &applied, patch); err != nil { 269 | return nil, err 270 | } 271 | 272 | return applied.Bytes(), nil 273 | } 274 | 275 | func (o *Options) verifyChecksum(updated []byte) error { 276 | checksum, err := checksumFor(o.getHash(), updated) 277 | if err != nil { 278 | return err 279 | } 280 | 281 | if !bytes.Equal(o.Checksum, checksum) { 282 | return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum) 283 | } 284 | return nil 285 | } 286 | 287 | func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) { 288 | if !h.Available() { 289 | return nil, errors.New("requested hash function not available") 290 | } 291 | hash := h.New() 292 | hash.Write(payload) // guaranteed not to error 293 | return hash.Sum([]byte{}), nil 294 | } 295 | -------------------------------------------------------------------------------- /apply_test.go: -------------------------------------------------------------------------------- 1 | package selfupdate 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/minio/selfupdate/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 | -------------------------------------------------------------------------------- /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/minio/selfupdate" 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 := selfupdate.Apply(resp.Body, selfupdate.Options{}) 25 | if err != nil { 26 | if rerr := selfupdate.RollbackError(err); rerr != nil { 27 | fmt.Println("Failed to rollback from bad update: %v", rerr) 28 | } 29 | } 30 | return err 31 | } 32 | 33 | # Binary Patching 34 | 35 | Go binaries can often be large. It can be advantageous to only ship a binary patch to a client 36 | instead of the complete program text of a new version. 37 | 38 | This example shows how to update a program with a bsdiff binary patch. Other patch formats 39 | may be applied by implementing the Patcher interface. 40 | 41 | import ( 42 | "encoding/hex" 43 | "io" 44 | 45 | "github.com/minio/selfupdate" 46 | ) 47 | 48 | func updateWithPatch(patch io.Reader) error { 49 | err := selfupdate.Apply(patch, selfupdate.Options{ 50 | Patcher: selfupdate.NewBSDiffPatcher() 51 | }) 52 | if err != nil { 53 | // error handling 54 | } 55 | return err 56 | } 57 | 58 | # Checksum Verification 59 | 60 | Updating executable code on a computer can be a dangerous operation unless you 61 | take the appropriate steps to guarantee the authenticity of the new code. While 62 | checksum verification is important, it should always be combined with signature 63 | verification (next section) to guarantee that the code came from a trusted party. 64 | 65 | selfupdate validates SHA256 checksums by default, but this is pluggable via the Hash 66 | property on the Options struct. 67 | 68 | This example shows how to guarantee that the newly-updated binary is verified to 69 | have an appropriate checksum (that was otherwise retrieved via a secure channel) 70 | specified as a hex string. 71 | 72 | import ( 73 | "crypto" 74 | _ "crypto/sha256" 75 | "encoding/hex" 76 | "io" 77 | 78 | "github.com/minio/selfupdate" 79 | ) 80 | 81 | func updateWithChecksum(binary io.Reader, hexChecksum string) error { 82 | checksum, err := hex.DecodeString(hexChecksum) 83 | if err != nil { 84 | return err 85 | } 86 | err = selfupdate.Apply(binary, selfupdate.Options{ 87 | Hash: crypto.SHA256, // this is the default, you don't need to specify it 88 | Checksum: checksum, 89 | }) 90 | if err != nil { 91 | // error handling 92 | } 93 | return err 94 | } 95 | 96 | # Cryptographic Signature Verification 97 | 98 | Cryptographic verification of new code from an update is an extremely important way to guarantee the 99 | security and integrity of your updates. 100 | 101 | Verification is performed by validating the signature of a hash of the new file. This 102 | means nothing changes if you apply your update with a patch. 103 | 104 | This example shows how to add signature verification to your updates. To make all of this work 105 | an application distributor must first create a public/private key pair and embed the public key 106 | into their application. When they issue a new release, the issuer must sign the new executable file 107 | with the private key and distribute the signature along with the selfupdate. 108 | 109 | import ( 110 | "crypto" 111 | _ "crypto/sha256" 112 | "encoding/hex" 113 | "io" 114 | 115 | "github.com/minio/selfupdate" 116 | ) 117 | 118 | func verifiedUpdate(binary io.Reader, hexChecksum string) { 119 | checksum, err := hex.DecodeString(hexChecksum) 120 | if err != nil { 121 | return err 122 | } 123 | opts := selfupdate.Options{ 124 | Checksum: checksum, 125 | Hash: crypto.SHA256, // this is the default, you don't need to specify it 126 | } 127 | err = selfupdate.Apply(binary, opts) 128 | if err != nil { 129 | // error handling 130 | } 131 | return err 132 | } 133 | 134 | # Building Single-File Go Binaries 135 | 136 | In order to update a Go application with selfupdate, you must distribute it as a single executable. 137 | This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates). 138 | In order to update applications like these, you'll want to make sure to embed those asset files into 139 | the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata 140 | 141 | # Non-Goals 142 | 143 | Mechanisms and protocols for determining whether an update should be applied and, if so, which one are 144 | out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io) 145 | for more complete solutions. 146 | 147 | selfupdate only works for self-updating applications that are distributed as a single binary, i.e. 148 | applications that do not have additional assets or dependency files. 149 | Updating application that are distributed as multiple on-disk files is out of scope, although this 150 | may change in future versions of this library. 151 | */ 152 | package selfupdate 153 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/minio/selfupdate 2 | 3 | go 1.14 4 | 5 | require ( 6 | aead.dev/minisign v0.2.0 7 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= 2 | aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= 3 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 4 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 5 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4= 6 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 7 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 8 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 9 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 10 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 11 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 15 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 19 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 20 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 21 | -------------------------------------------------------------------------------- /hide_noop.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package selfupdate 4 | 5 | func hideFile(path string) error { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /hide_windows.go: -------------------------------------------------------------------------------- 1 | package selfupdate 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/.gitignore: -------------------------------------------------------------------------------- 1 | test.* -------------------------------------------------------------------------------- /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/minio/selfupdate/f11e74f84ca7effe613f65b578058ba53c14b092/internal/binarydist/testdata/sample.new -------------------------------------------------------------------------------- /internal/binarydist/testdata/sample.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minio/selfupdate/f11e74f84ca7effe613f65b578058ba53c14b092/internal/binarydist/testdata/sample.old -------------------------------------------------------------------------------- /internal/binarydist/testdata/sample.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minio/selfupdate/f11e74f84ca7effe613f65b578058ba53c14b092/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 android 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", "android": 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 | -------------------------------------------------------------------------------- /minisign.go: -------------------------------------------------------------------------------- 1 | package selfupdate 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | 8 | "aead.dev/minisign" 9 | ) 10 | 11 | type Verifier struct { 12 | publicKey minisign.PublicKey 13 | signature minisign.Signature 14 | } 15 | 16 | func (v *Verifier) LoadFromURL(signatureURL string, passphrase string, transport http.RoundTripper) error { 17 | var publicKey minisign.PublicKey 18 | if err := publicKey.UnmarshalText([]byte(passphrase)); err != nil { 19 | return err 20 | } 21 | 22 | client := &http.Client{Transport: transport} 23 | req, err := http.NewRequest(http.MethodGet, signatureURL, nil) 24 | if err != nil { 25 | return err 26 | } 27 | resp, err := client.Do(req) 28 | if err != nil { 29 | return err 30 | } 31 | defer resp.Body.Close() 32 | if resp.StatusCode != http.StatusOK { 33 | return errors.New(resp.Status) 34 | } 35 | 36 | const MaxSize = 1 << 20 37 | b, err := io.ReadAll(io.LimitReader(resp.Body, MaxSize)) 38 | if err != nil { 39 | return err 40 | } 41 | var signature minisign.Signature 42 | if err = signature.UnmarshalText(b); err != nil { 43 | return err 44 | } 45 | v.publicKey, v.signature = publicKey, signature 46 | return nil 47 | } 48 | 49 | func (v *Verifier) LoadFromFile(signaturePath string, passphrase string) error { 50 | var publicKey minisign.PublicKey 51 | if err := publicKey.UnmarshalText([]byte(passphrase)); err != nil { 52 | return err 53 | } 54 | signature, err := minisign.SignatureFromFile(signaturePath) 55 | if err != nil { 56 | return err 57 | } 58 | v.publicKey, v.signature = publicKey, signature 59 | return nil 60 | } 61 | 62 | func NewVerifier() *Verifier { 63 | return &Verifier{} 64 | } 65 | 66 | func (v *Verifier) Verify(bin []byte) error { 67 | signature, err := v.signature.MarshalText() 68 | if err != nil { 69 | return err 70 | } 71 | if !minisign.Verify(v.publicKey, bin, signature) { 72 | return errors.New("selfupdate: signature verification failed") 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /minisign_test.go: -------------------------------------------------------------------------------- 1 | package selfupdate 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | ) 7 | 8 | func TestMinisign(t *testing.T) { 9 | v := NewVerifier() 10 | err := v.LoadFromFile("LICENSE.minisig", "RWQhjNB8gjlNDQYRsRiGEzKTtGwzkcFLRMiSEy+texbTAVMvsgFLLfSr") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | buf, err := ioutil.ReadFile("LICENSE") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | if err = v.Verify(buf); err != nil { 21 | t.Fatal(err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /patcher.go: -------------------------------------------------------------------------------- 1 | package selfupdate 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/minio/selfupdate/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 | -------------------------------------------------------------------------------- /test.key: -------------------------------------------------------------------------------- 1 | untrusted comment: minisign encrypted secret key 2 | RWRTY0IyG1x0dmDUEt6W4pVJdOc68t4Qw5WGYE2YvmQKDVOhYHEAAAACAAAAAAAAAEAAAAAAriScLG3bvMtySsBgoKpDAFZwjsEk7mftUovvG1i31XD8W+9a+lOBUPaC8CSUma9AmGuKFWCNsYRo1acAvvtW/zMXd9DamLaRhfvyJ+h7aUYpZsaVTBzczwATrvsIJkXclKmGe4eIXV0= 3 | -------------------------------------------------------------------------------- /test.pub: -------------------------------------------------------------------------------- 1 | untrusted comment: minisign public key D4D39827CD08C21 2 | RWQhjNB8gjlNDQYRsRiGEzKTtGwzkcFLRMiSEy+texbTAVMvsgFLLfSr 3 | --------------------------------------------------------------------------------