├── .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 | [](https://pkg.go.dev/github.com/minio/selfupdate?tab=doc) [](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 |
--------------------------------------------------------------------------------