From a9b338ffecb28d06f9d52a9024f01c5a0d28f52f Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 29 Oct 2020 21:52:40 +0000 Subject: [PATCH] Authenticate releases using the embedded verification key. Fixes #15. Signed-off-by: Piotr Sikora --- WORKSPACE | 28 ++++++++++++++++++++ core/repositories.go | 2 +- go.mod | 1 + go.sum | 7 +++++ httputil/BUILD | 1 + httputil/httputil.go | 36 ++++++++++++++++++++++++- repositories/gcs.go | 60 +++++++++++++++++++++++++++++++++++++++--- repositories/github.go | 2 +- 8 files changed, 131 insertions(+), 6 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index dba44563..4b6f6b21 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -34,6 +34,34 @@ go_repository( version = "v0.24.3", ) +go_repository( + name = "org_golang_x_crypto", + importpath = "golang.org/x/crypto", + sum = "h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=", + version = "v0.0.0-20201016220609-9e8e0b390897", +) + +go_repository( + name = "org_golang_x_net", + importpath = "golang.org/x/net", + sum = "h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=", + version = "v0.0.0-20190404232315-eb5bcb51f2a3", +) + +go_repository( + name = "org_golang_x_sys", + importpath = "golang.org/x/sys", + sum = "h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=", + version = "v0.0.0-20190412213103-97732733099d", +) + +go_repository( + name = "org_golang_x_text", + importpath = "golang.org/x/text", + sum = "h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=", + version = "v0.3.0", +) + gazelle_dependencies() go_repository( diff --git a/core/repositories.go b/core/repositories.go index 0fb0a4da..624e88fb 100644 --- a/core/repositories.go +++ b/core/repositories.go @@ -175,7 +175,7 @@ func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile s } url := fmt.Sprintf("%s/%s/%s", baseURL, version, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, "", "", destDir, destFile) } // CreateRepositories creates a new Repositories instance with the given repositories. Any nil repository will be replaced by a dummy repository that raises an error whenever a download is attempted. diff --git a/go.mod b/go.mod index 1aac2062..a3d9e3b8 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/bazelbuild/rules_go v0.24.3 github.com/hashicorp/go-version v1.2.1 github.com/mitchellh/go-homedir v1.1.0 + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 ) diff --git a/go.sum b/go.sum index a1d9d191..735a227b 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,10 @@ github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pB github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/httputil/BUILD b/httputil/BUILD index 7374867f..3598cc92 100644 --- a/httputil/BUILD +++ b/httputil/BUILD @@ -5,4 +5,5 @@ go_library( srcs = ["httputil.go"], importpath = "github.com/bazelbuild/bazelisk/httputil", visibility = ["//visibility:public"], + deps = ["@org_golang_x_crypto//openpgp:go_default_library"], ) diff --git a/httputil/httputil.go b/httputil/httputil.go index 5a2b09d9..a73f6823 100644 --- a/httputil/httputil.go +++ b/httputil/httputil.go @@ -3,12 +3,14 @@ package httputil import ( "fmt" + "golang.org/x/crypto/openpgp" "io" "io/ioutil" "log" "net/http" "os" "path/filepath" + "strings" "time" ) @@ -51,7 +53,7 @@ func ReadRemoteFile(url string, token string) ([]byte, error) { } // DownloadBinary downloads a file from the given URL into the specified location, marks it executable and returns its full path. -func DownloadBinary(originURL, destDir, destFile string) (string, error) { +func DownloadBinary(originURL, signatureURL, verificationKey, destDir, destFile string) (string, error) { err := os.MkdirAll(destDir, 0755) if err != nil { return "", fmt.Errorf("could not create directory %s: %v", destDir, err) @@ -91,6 +93,38 @@ func DownloadBinary(originURL, destDir, destFile string) (string, error) { return "", fmt.Errorf("could not chmod file %s: %v", tmpfile.Name(), err) } + if signatureURL != "" && verificationKey != "" { + signature, err := getClient().Get(signatureURL) + if err != nil { + return "", fmt.Errorf("HTTP GET %s failed: %v", signatureURL, err) + } + defer signature.Body.Close() + + if signature.StatusCode != 200 { + return "", fmt.Errorf("HTTP GET %s failed with error %v", signatureURL, signature.StatusCode) + } + + keys, err := openpgp.ReadArmoredKeyRing(strings.NewReader(verificationKey)) + if err != nil { + return "", fmt.Errorf("failed to load the embedded Verification Key") + } + + if len(keys) != 1 { + return "", fmt.Errorf("failed to load the embedded Verification Key") + } + + tmpfile.Seek(0, io.SeekStart) + + entity, err := openpgp.CheckDetachedSignature(keys, tmpfile, signature.Body) + if err != nil { + return "", fmt.Errorf("failed to verify the downloaded file using signature from %s", signatureURL) + } + + for _, identity := range entity.Identities { + log.Printf("Signed by %s", identity.Name) + } + } + tmpfile.Close() err = os.Rename(tmpfile.Name(), destinationPath) if err != nil { diff --git a/repositories/gcs.go b/repositories/gcs.go index 57dc28f2..294698da 100644 --- a/repositories/gcs.go +++ b/repositories/gcs.go @@ -19,6 +19,60 @@ const ( candidateBaseURL = "https://releases.bazel.build" nonCandidateBaseURL = "https://storage.googleapis.com/bazel-builds/artifacts" lastGreenBaseURL = "https://storage.googleapis.com/bazel-untrusted-builds/last_green_commit/" + verificationKey = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFdEmzkBEACzj8tMYUau9oFZWNDytcQWazEO6LrTTtdQ98d3JcnVyrpT16yg +I/QfGXA8LuDdKYpUDNjehLtBL3IZp4xe375Jh8v2IA2iQ5RXGN+lgKJ6rNwm15Kr +qYeCZlU9uQVpZuhKLXsWK6PleyQHjslNUN/HtykIlmMz4Nnl3orT7lMI5rsGCmk0 +1Kth0DFh8SD9Vn2G4huddwxM8/tYj1QmWPCTgybATNuZ0L60INH8v6+J2jJzViVc +NRnR7mpouGmRy/rcr6eY9QieOwDou116TrVRFfcBRhocCI5b6uCRuhaqZ6Qs28Bx +4t5JVksXJ7fJoTy2B2s/rPx/8j4MDVEdU8b686ZDHbKYjaYBYEfBqePXScp8ndul +XWwS2lcedPihOUl6oQQYy59inWIpxi0agm0MXJAF1Bc3ToSQdHw/p0Y21kYxE2pg +EaUeElVccec5poAaHSPprUeej9bD9oIC4sMCsLs7eCQx2iP+cR7CItz6GQtuZrvS +PnKju1SKl5iwzfDQGpi6u6UAMFmc53EaH05naYDAigCueZ+/2rIaY358bECK6/VR +kyrBqpeq6VkWUeOkt03VqoPzrw4gEzRvfRtLj+D2j/pZCH3vyMYHzbaaXBv6AT0e +RmgtGo9I9BYqKSWlGEF0D+CQ3uZfOyovvrbYqNaHynFBtrx/ZkM82gMA5QARAQAB +tEdCYXplbCBEZXZlbG9wZXIgKEJhemVsIEFQVCByZXBvc2l0b3J5IGtleSkgPGJh +emVsLWRldkBnb29nbGVncm91cHMuY29tPokCVQQTAQgAPwIbAwYLCQgHAwIGFQgC +CQoLBBYCAwECHgECF4AWIQRxodDvz+tigf0EN8k9WRm0SEV+4AUCXsoWGgUJC0fh +4QAKCRA9WRm0SEV+4NDCD/9c5rhZREBlikdi5QYRq1YOkwzJLXFoVe0FonEwMuWK +fQzT/rIwyh14tssptU5+eXwTEXL0ZDskgzvrFSpzjQZzcSG/gzNCATNfrZpC2nfE +SxMKOeIwQedn26YIHCI8s9tEQ7BSvfBfJgqfIo3IURhmfzNMj+qszca+3IDYAlAy +8lxUVbJcIQ0apnAdnIadtydzca56mMN7ma+btddaWLpAdyfUvQ/Zsx3TYYLF7inQ +km0JpzISN0fGngzGNDGNmtHNhCdSpyfkr+7fvpbKAYkSH7uZ1AIPDyHdLIwDQnX2 +kbLRkxKncKGSDhUSdlJTl0x36cU+xmgO15FFdOyk3BUfrlfDrgXIBjeX8KNh9TV6 +HgFFR/mNONoJ93ZvZQNO2s1gbPZJe3VJ1Q5PMLW1sdl8q8JthBwT/5TJ1k8E5VYj +jAc8dl+RAALxqj+eo5xI45o1FdV5s1aGDjbwFoCIhGCy2zaog1q5wnhmEptAAD0S +TVbJSpwNiLlPIcGVaCjXp8Ow3SzOGTRKIjFTO/I6FiSJOpgfri07clXmnb4ETjou +mUdglg8/8nQ120zHEOqoSzzIbTNUDjNZY8SuY6Ig3/ObQ/JAFS0i6h74KLfXUZzn +uETY7KURLdyPAhL37Hb9FDhvkJCUO/l6eqDh9jk1JjB7Cvb7hEvnbvDrr2hWNAL7 +RrkCDQRXRJs5ARAA55/1VBlDpV/ElUyLmRyPCz/V+msHdinyw4Mv5DJQupuZwlMy +vxPPzc7GmsIfk1zuOzDWirNs22r43ak6dsAvpcU+iVBi46MqUcbNtC+kfxlKiToD +PCs82rdfCgHT7XYDzrCWlqNQ9++BqM2OYRIxyEucizeofWPlrJUgKvu8fWLVZ6bY +n4L/PqAhobhuSjRcoB5Tp81hGa4cscKIGIqhymfnguaY8viJ83tHPUqQJoApNPy8 +q1pWHSDV6zBv71beqV2b6cBzp7VqNYOIuqE6ZNBFWuCG3zRc9ia2/bHxx2TGAQJt +PpPzitm0xkB3GGN06YnnSCE+f2j+7F0IO6uFlSy7ho0PoSFbDgR91kJK3S0ZBZx4 +H21cIpWWBzf9Nd1M4H3O7KhnGSZDq6+tXZ9/F/ZUvCZHpQlJewDPY9315Ymacf5C +Zk8xeE5UUIxFMdOxF8B7Itb6rbFWv+tzWdX/0/M8/b0ZJhVvngWzuh/agdS4E5an +f7ahGWM96jPRIQEb9DRN2YGp9hOiX2sZqkhxE5zWqD2gdXp2ZAxMCTHf4ijzOVsO +nde7b5BqC0JL73gNwf1iOHyCAzqGiFfah8/odBTDhMsdVMsjSIxzcwlwRnzy+hBs +dYpP19ieJCMoERJTbUgSspPdhY/Y4ChzlFHjiAKYT6vXiYcKS04stCtHqwEAEQEA +AYkCPAQYAQgAJgIbDBYhBHGh0O/P62KB/QQ3yT1ZGbRIRX7gBQJeyhYlBQkLR+Hs +AAoJED1ZGbRIRX7g3Y8P/iuOAHmyCMeSELvUs9ZvLYJKGzmz67R8fJSmgst/Bs3p +dWCAjGE56M6UgZzHXK+fBRWFPDOXT64XNq0UIG7tThthwe4Gdvg/5rWG61Pe/vCZ +2FkMAlEMkuufZYMcw9jItHMKLcYyW/jtN9EzCX+vM6SZlu4o8la5rCIBEaiKfzft +a/dRMjW+RqQnU31NQCDAy3zoGUCQumJtv3GVbMYHIrRZua2yyNo9Iborh2SVdBbK +v9WJKH4JcCHd0/XDGdys6EXeATIIRxchumkmxpIg87OhsC0n5yuH1FnFIFQEjbYX +bb46F7ZFT+8Tov+lgMEw4CZmps4uvvZlKbIH4Zi/ULiobwvm2ad3nejWICmGmHYz +ro6t08hdcY6GnOzCpDwx9yHechMCkU3KEE98nb/CxcmA4VzDHudTJe7o0OyaSarh +6D5WcXf7D9FfcKmUD9xaCsfXh66OCksMVGE1JctrO1wQTF2jTdTUq7mmi30tlM+o +JjVk65OSOd4JYol8auzE4oXOfsNzXbyvj7WzM1v5m7C45jOL+Ly7I3IUzZNfF41J +AMmSd73EOoR9YH4qTrL3jx69Ekf7ww70Qea5enLE8xUgQfGTOaEHxkFcEovmzv54 +6IVe083iK8alXD/9OUTaDY9NwMnOn1K1aU2XOfliGGLgwwaHg+wVFh5rZIHsDl7v +=Embu +-----END PGP PUBLIC KEY BLOCK----- +` ) var ( @@ -106,7 +160,7 @@ func (gcs *GCSRepo) DownloadRelease(version, destDir, destFile string) (string, } url := fmt.Sprintf("%s/%s/release/%s", candidateBaseURL, version, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, url+".sig", verificationKey, destDir, destFile) } func (gcs *GCSRepo) removeCandidates(history []string, lastN int) ([]string, error) { @@ -189,7 +243,7 @@ func (gcs *GCSRepo) DownloadCandidate(version, destDir, destFile string) (string baseVersion := versionComponents[0] rcVersion := "rc" + versionComponents[1] url := fmt.Sprintf("%s/%s/%s/%s", candidateBaseURL, baseVersion, rcVersion, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, url+".sig", verificationKey, destDir, destFile) } // CommitRepo @@ -210,5 +264,5 @@ func (gcs *GCSRepo) GetLastGreenCommit(bazeliskHome string, downstreamGreen bool func (gcs *GCSRepo) DownloadAtCommit(commit, destDir, destFile string) (string, error) { log.Printf("Using unreleased version at commit %s", commit) url := fmt.Sprintf("%s/%s/%s/bazel", nonCandidateBaseURL, platforms.GetPlatform(), commit) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, "", "", destDir, destFile) } diff --git a/repositories/github.go b/repositories/github.go index 0be03d64..1034e31f 100644 --- a/repositories/github.go +++ b/repositories/github.go @@ -59,5 +59,5 @@ func (gh *GitHubRepo) DownloadVersion(fork, version, destDir, destFile string) ( return "", err } url := fmt.Sprintf(urlPattern, fork, version, filename) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, "", "", destDir, destFile) }