cmd/release: upload file.sha256 alongside release binaries

This makes it easier to programmatically verify the
integrity of the binaries.

Fixes golang/go#14385

Change-Id: Ie3688d9b07412018b74d6189ab0d7bc72ef50398
Reviewed-on: https://go-review.googlesource.com/19783
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/release/upload.go b/cmd/release/upload.go
index 8c4cd74..4c4c011 100644
--- a/cmd/release/upload.go
+++ b/cmd/release/upload.go
@@ -84,15 +84,15 @@
 		return err
 	}
 	base := filepath.Base(filename)
+	checksum := fmt.Sprintf("%x", sha256.Sum256(file))
 
-	// Upload the file to Google Cloud Storage.
-	wr := c.Bucket(storageBucket).Object(base).NewWriter(ctx)
-	wr.ACL = []storage.ACLRule{
-		{Entity: storage.AllUsers, Role: storage.RoleReader},
+	// Upload file to Google Cloud Storage.
+	if err := putObject(ctx, c, base, file); err != nil {
+		return fmt.Errorf("uploading %q: %v", base, err)
 	}
-	wr.Write(file)
-	if err := wr.Close(); err != nil {
-		return fmt.Errorf("uploading file: %v", err)
+	// Upload file.sha256.
+	if err := putObject(ctx, c, base+".sha256", []byte(checksum)); err != nil {
+		return fmt.Errorf("uploading %q: %v", base+".sha256", err)
 	}
 
 	// Post file details to golang.org.
@@ -110,7 +110,7 @@
 		Version:        version,
 		OS:             b.OS,
 		Arch:           b.Arch,
-		ChecksumSHA256: fmt.Sprintf("%x", sha256.Sum256(file)),
+		ChecksumSHA256: checksum,
 		Size:           int64(len(file)),
 		Kind:           kind,
 	})
@@ -132,6 +132,22 @@
 
 }
 
+func putObject(ctx context.Context, c *storage.Client, name string, body []byte) error {
+	wr := c.Bucket(storageBucket).Object(name).NewWriter(ctx)
+	wr.ACL = []storage.ACLRule{
+		{Entity: storage.AllUsers, Role: storage.RoleReader},
+		// If you don't give the owners access, the web UI seems to
+		// have a bug and doesn't have access to see that it's public,
+		// so won't render the "Shared Publicly" link. So we do that,
+		// even though it's dumb and unnecessary otherwise:
+		{Entity: storage.ACLEntity("project-owners-" + projectID), Role: storage.RoleOwner},
+	}
+	if _, err := wr.Write(body); err != nil {
+		return err
+	}
+	return wr.Close()
+}
+
 func storageClient(ctx context.Context) (*storage.Client, error) {
 	file := filepath.Join(os.Getenv("HOME"), "keys", "golang-org.service.json")
 	blob, err := ioutil.ReadFile(file)