From 116dfa144db56022d8539a077a2c3a33c2e9a41c Mon Sep 17 00:00:00 2001 From: Tobias Kohlbau Date: Tue, 6 Nov 2018 16:28:06 +0100 Subject: [PATCH] Add hash and signature validation Go-self-update lacks support for checking integrity of downloaded files. For more advanced situation it's necessary to validate the hash or verify against public signatures. This patch adds support for SHA2 hash and ECDSA PublicKey signature validation. SHA2 uses file with suffix `.sha256`, whereas ECDSA uses `.sig` file endings. See `selfupdate/validate_test.go` for examples. Signed-off-by: Tobias Kohlbau --- README.md | 39 ++++++++++ selfupdate/detect.go | 20 +++++ selfupdate/release.go | 2 + selfupdate/testdata/Test.crt | 9 +++ selfupdate/testdata/Test.pem | 14 ++++ selfupdate/testdata/foo.zip.sha256 | 1 + selfupdate/testdata/foo.zip.sig | Bin 0 -> 71 bytes selfupdate/update.go | 43 ++++++++++- selfupdate/updater.go | 9 ++- selfupdate/validate.go | 73 ++++++++++++++++++ selfupdate/validate_test.go | 114 +++++++++++++++++++++++++++++ 11 files changed, 317 insertions(+), 7 deletions(-) create mode 100644 selfupdate/testdata/Test.crt create mode 100644 selfupdate/testdata/Test.pem create mode 100644 selfupdate/testdata/foo.zip.sha256 create mode 100644 selfupdate/testdata/foo.zip.sig create mode 100644 selfupdate/validate.go create mode 100644 selfupdate/validate_test.go diff --git a/README.md b/README.md index d5fe0dd..92a5f2f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ GitHub and replaces itself. - Many archive and compression formats are supported (zip, tar, gzip, xzip) - Support private repositories - Support [GitHub Enterprise][] +- Support hash, signature validation And small wrapper CLIs are provided: @@ -290,6 +291,44 @@ In summary, structure of releases on GitHub looks like: Tags which don't contain a version number are ignored (i.e. `nightly`). And releases marked as `pre-release` are also ignored. +### Hash or Signature Validation + +go-github-selfupdate supports hash or signature validatiom of the downloaded files. It comes +with support for sha256 hashes or ECDSA signatures. In addition to internal functions the +user can implement the `Validator` interface for own validation mechanisms. + +```go +// Validator represents an interface which enables additional validation of releases. +type Validator interface { + // Validate validates release bytes against an additional asset bytes. + // See SHA2Validator or ECDSAValidator for more information. + Validate(release, asset []byte) error + // Suffix describes the additional file ending which is used for finding the + // additional asset. + Suffix() string +} +``` + +## SHA256 + +To verify the integrity by SHA256 generate a hash sum and save it within a file which has the +same naming as original file with the suffix `.sha256`. +For e.g. use sha256sum, the file `selfupdate/testdata/foo.zip.sha256` is generated with: +```shell +sha256sum foo.zip > foo.zip.sha256 +``` + +## ECDSA +To verify the signature by ECDSA generate a signature and save it within a file which has the +same naming as original file with the suffix `.sig`. +For e.g. use openssl, the file `selfupdate/testdata/foo.zip.sig` is generated with: +```shell +openssl dgst -sha256 -sign Test.pem -out foo.zip.sig foo.zip +``` + +go-github-selfupdate makes use of go internal crypto package. Therefore the used private key +has to be compatbile with FIPS 186-3. + ### Development #### Running tests diff --git a/selfupdate/detect.go b/selfupdate/detect.go index a20a645..a8c61fd 100644 --- a/selfupdate/detect.go +++ b/selfupdate/detect.go @@ -59,6 +59,15 @@ func findAssetFromReleasse(rel *github.RepositoryRelease, suffixes []string, tar return nil, semver.Version{}, false } +func findValidationAsset(rel *github.RepositoryRelease, validationName string) (*github.ReleaseAsset, bool) { + for _, asset := range rel.Assets { + if asset.GetName() == validationName { + return &asset, true + } + } + return nil, false +} + func findReleaseAndAsset(rels []*github.RepositoryRelease, targetVersion string) (*github.RepositoryRelease, *github.ReleaseAsset, semver.Version, bool) { // Generate candidates suffixes := make([]string, 0, 2*7*2) @@ -143,6 +152,7 @@ func (up *Updater) DetectVersion(slug string, version string) (release *Release, url, asset.GetSize(), asset.GetID(), + -1, rel.GetHTMLURL(), rel.GetBody(), rel.GetName(), @@ -150,6 +160,16 @@ func (up *Updater) DetectVersion(slug string, version string) (release *Release, repo[0], repo[1], } + + if up.validator != nil { + validationName := asset.GetName()+up.validator.Suffix() + validationAsset, ok := findValidationAsset(rel, validationName) + if !ok { + return nil, false, fmt.Errorf("Failed finding validation file %q", validationName) + } + release.ValidationAssetID = validationAsset.GetID() + } + return release, true, nil } diff --git a/selfupdate/release.go b/selfupdate/release.go index 9ee0cb0..014ac47 100644 --- a/selfupdate/release.go +++ b/selfupdate/release.go @@ -16,6 +16,8 @@ type Release struct { AssetByteSize int // AssetID is the ID of the asset on GitHub AssetID int64 + // ValidationAssetID is the ID of additional validaton asset on GitHub + ValidationAssetID int64 // URL is a URL to release page for browsing URL string // ReleaseNotes is a release notes of the release diff --git a/selfupdate/testdata/Test.crt b/selfupdate/testdata/Test.crt new file mode 100644 index 0000000..7f4eff3 --- /dev/null +++ b/selfupdate/testdata/Test.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBLzCB1qADAgECAgglJIDaK1tbjjAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRU +ZXN0MCAXDTE4MTEwNjE1MTcwMFoYDzIxMTgxMTA2MTUxNzAwWjAPMQ0wCwYDVQQD +EwRUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs8fjo/Mi5A3c2v2YxV6A +QPJnr70qYMEpsmqn0BTcI8RhZUgB46tWqeDYdO15yQKbZjfI/dr0fvS21jyW0GSX +rKMaMBgwCwYDVR0PBAQDAgXgMAkGA1UdEwQCMAAwCgYIKoZIzj0EAwIDSAAwRQIh +AI1pUr0nrw3m++sR8HEBoejM5Qh1QJA7gF9y1jY6rc/aAiALFPJXckSLAQuq5IvQ +7cugOPws7/OoUo1124LKPugISg== +-----END CERTIFICATE----- diff --git a/selfupdate/testdata/Test.pem b/selfupdate/testdata/Test.pem new file mode 100644 index 0000000..c240365 --- /dev/null +++ b/selfupdate/testdata/Test.pem @@ -0,0 +1,14 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJvTkRedVrQDNjCb9/RfVjzRwz8S059Y1J6w2N8gy8jVoAoGCCqGSM49 +AwEHoUQDQgAEs8fjo/Mi5A3c2v2YxV6AQPJnr70qYMEpsmqn0BTcI8RhZUgB46tW +qeDYdO15yQKbZjfI/dr0fvS21jyW0GSXrA== +-----END EC PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBLzCB1qADAgECAgglJIDaK1tbjjAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRU +ZXN0MCAXDTE4MTEwNjE1MTcwMFoYDzIxMTgxMTA2MTUxNzAwWjAPMQ0wCwYDVQQD +EwRUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs8fjo/Mi5A3c2v2YxV6A +QPJnr70qYMEpsmqn0BTcI8RhZUgB46tWqeDYdO15yQKbZjfI/dr0fvS21jyW0GSX +rKMaMBgwCwYDVR0PBAQDAgXgMAkGA1UdEwQCMAAwCgYIKoZIzj0EAwIDSAAwRQIh +AI1pUr0nrw3m++sR8HEBoejM5Qh1QJA7gF9y1jY6rc/aAiALFPJXckSLAQuq5IvQ +7cugOPws7/OoUo1124LKPugISg== +-----END CERTIFICATE----- diff --git a/selfupdate/testdata/foo.zip.sha256 b/selfupdate/testdata/foo.zip.sha256 new file mode 100644 index 0000000..9a3192d --- /dev/null +++ b/selfupdate/testdata/foo.zip.sha256 @@ -0,0 +1 @@ +e412095724426c984940efde02ea000251a12b37506c977341e0a07600dbfcb6 foo.zip diff --git a/selfupdate/testdata/foo.zip.sig b/selfupdate/testdata/foo.zip.sig new file mode 100644 index 0000000000000000000000000000000000000000..03d06a6181df01497e8a48cb10a666a0cf9c5e6e GIT binary patch literal 71 zcmV-N0J#4!MFJpLedtJ}XCKoCUj