diff --git a/WORKSPACE b/WORKSPACE index 49c72bf0..1896b88f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -42,6 +42,34 @@ go_repository( version = "v1.1.0", ) +go_repository( + name = "org_golang_x_crypto", + importpath = "golang.org/x/crypto", + sum = "h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=", + version = "v0.0.0-20211215153901-e495a2d5b3d3", +) + +go_repository( + name = "org_golang_x_net", + importpath = "golang.org/x/net", + sum = "h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=", + version = "v0.0.0-20211112202133-69e39bad7dc2", +) + +go_repository( + name = "org_golang_x_sys", + importpath = "golang.org/x/sys", + sum = "h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=", + version = "v0.0.0-20210615035016-665e8c7367d1", +) + +go_repository( + name = "org_golang_x_term", + importpath = "golang.org/x/term", + sum = "h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=", + version = "v0.0.0-20201126162022-7de9c90e9dd1", +) + go_rules_dependencies() go_register_toolchains(version = "1.16.4") diff --git a/core/repositories.go b/core/repositories.go index 8a2919c0..8854f0d6 100644 --- a/core/repositories.go +++ b/core/repositories.go @@ -224,7 +224,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 112afa02..507c89b6 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/bazelbuild/rules_go v0.29.0 github.com/hashicorp/go-version v1.3.0 github.com/mitchellh/go-homedir v1.1.0 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 ) diff --git a/go.sum b/go.sum index 20b426c2..6db32477 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,10 @@ github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04 github.com/hashicorp/go-version v1.3.0/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-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/httputil/BUILD b/httputil/BUILD index 44049d0f..3f1520b7 100644 --- a/httputil/BUILD +++ b/httputil/BUILD @@ -12,6 +12,7 @@ go_library( ], importpath = "github.com/bazelbuild/bazelisk/httputil", visibility = ["//visibility:public"], + deps = ["@org_golang_x_crypto//openpgp:go_default_library"], ) go_test( diff --git a/httputil/httputil.go b/httputil/httputil.go index b4f5b872..2c1aaf0c 100644 --- a/httputil/httputil.go +++ b/httputil/httputil.go @@ -3,6 +3,7 @@ package httputil import ( "fmt" + "golang.org/x/crypto/openpgp" "io" "io/ioutil" "log" @@ -12,6 +13,7 @@ import ( "path/filepath" "regexp" "strconv" + "strings" "time" ) @@ -134,7 +136,7 @@ func parseRetryHeader(value string) (time.Duration, 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) @@ -174,6 +176,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 := 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 82e33ddc..846ad12f 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 ( @@ -127,7 +181,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) { @@ -216,7 +270,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 @@ -237,5 +291,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 e3134cc4..16c6c723 100644 --- a/repositories/github.go +++ b/repositories/github.go @@ -87,7 +87,7 @@ 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) } // GetRollingVersions returns a list of all available rolling release versions.