treewide: move build helper to more fitting places

Change-Id: I3d0cfe9283222d403ae369ec9db09201ad511e15
Reviewed-on: https://review.monogon.dev/c/monogon/+/3327
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/build/mkverity/BUILD.bazel b/osbase/build/mkverity/BUILD.bazel
new file mode 100644
index 0000000..04a8a54
--- /dev/null
+++ b/osbase/build/mkverity/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_binary(
+    name = "mkverity",
+    embed = [":mkverity_lib"],
+    visibility = [
+        "//metropolis/installer/test/testos:__pkg__",
+        "//metropolis/node:__pkg__",
+        "//metropolis/node/core/update/e2e/testos:__pkg__",
+    ],
+)
+
+go_library(
+    name = "mkverity_lib",
+    srcs = ["mkverity.go"],
+    importpath = "source.monogon.dev/osbase/build/mkverity",
+    visibility = ["//visibility:private"],
+    deps = ["//osbase/verity"],
+)
diff --git a/osbase/build/mkverity/mkverity.go b/osbase/build/mkverity/mkverity.go
new file mode 100644
index 0000000..ff2807b
--- /dev/null
+++ b/osbase/build/mkverity/mkverity.go
@@ -0,0 +1,152 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This package implements a command line tool that creates dm-verity hash
+// images at a selected path, given an existing data image. The tool
+// outputs a Verity mapping table on success.
+//
+// For more information, see:
+// - source.monogon.dev/osbase/verity
+// - https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+
+	"source.monogon.dev/osbase/verity"
+)
+
+// createImage creates a dm-verity target image by combining the input image
+// with Verity metadata. Contents of the data image are copied to the output
+// image. Then, the same contents are verity-encoded and appended to the
+// output image. The verity superblock is written only if wsb is true. It
+// returns either a dm-verity target table, or an error.
+func createImage(dataImagePath, outputImagePath string, wsb bool) (*verity.MappingTable, error) {
+	// Hardcode both the data block size and the hash block size as 4096 bytes.
+	bs := uint32(4096)
+
+	// Open the data image for reading.
+	dataImage, err := os.Open(dataImagePath)
+	if err != nil {
+		return nil, fmt.Errorf("while opening the data image: %w", err)
+	}
+	defer dataImage.Close()
+
+	// Check that the data image is well-formed.
+	ds, err := dataImage.Stat()
+	if err != nil {
+		return nil, fmt.Errorf("while stat-ing the data image: %w", err)
+	}
+	if !ds.Mode().IsRegular() {
+		return nil, fmt.Errorf("the data image must be a regular file")
+	}
+	if ds.Size()%int64(bs) != 0 {
+		return nil, fmt.Errorf("the data image must end on a %d-byte block boundary", bs)
+	}
+
+	// Create an empty hash image file.
+	outputImage, err := os.OpenFile(outputImagePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
+	if err != nil {
+		return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
+	}
+	defer outputImage.Close()
+
+	// Copy the input data into the output file, then rewind dataImage to be read
+	// again by the Verity encoder.
+	_, err = io.Copy(outputImage, dataImage)
+	if err != nil {
+		return nil, err
+	}
+	_, err = dataImage.Seek(0, os.SEEK_SET)
+	if err != nil {
+		return nil, err
+	}
+
+	// Write outputImage contents. Start with initializing a verity encoder,
+	// seting outputImage as its output.
+	v, err := verity.NewEncoder(outputImage, bs, bs, wsb)
+	if err != nil {
+		return nil, fmt.Errorf("while initializing a verity encoder: %w", err)
+	}
+	// Hash the contents of dataImage, block by block.
+	_, err = io.Copy(v, dataImage)
+	if err != nil {
+		return nil, fmt.Errorf("while reading the data image: %w", err)
+	}
+	// The resulting hash tree won't be written until Close is called.
+	err = v.Close()
+	if err != nil {
+		return nil, fmt.Errorf("while writing the hash image: %w", err)
+	}
+
+	// Return an encoder-generated verity mapping table, containing the salt and
+	// the root hash. First, calculate the starting hash block by dividing the
+	// data image size by the encoder data block size.
+	hashStart := ds.Size() / int64(bs)
+	mt, err := v.MappingTable(dataImagePath, outputImagePath, hashStart)
+	if err != nil {
+		return nil, fmt.Errorf("while querying for the mapping table: %w", err)
+	}
+	return mt, nil
+}
+
+var (
+	input           = flag.String("input", "", "input disk image (required)")
+	output          = flag.String("output", "", "output disk image with Verity metadata appended (required)")
+	dataDeviceAlias = flag.String("data_alias", "", "data device alias used in the mapping table")
+	hashDeviceAlias = flag.String("hash_alias", "", "hash device alias used in the mapping table")
+	table           = flag.String("table", "", "a file the mapping table will be saved to; disables stdout")
+)
+
+func main() {
+	flag.Parse()
+
+	// Ensure that required parameters were provided before continuing.
+	if *input == "" {
+		log.Fatalf("-input must be set.")
+	}
+	if *output == "" {
+		log.Fatalf("-output must be set.")
+	}
+
+	// Build the image.
+	mt, err := createImage(*input, *output, false)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Patch the device names, if alternatives were provided.
+	if *dataDeviceAlias != "" {
+		mt.DataDevicePath = *dataDeviceAlias
+	}
+	if *hashDeviceAlias != "" {
+		mt.HashDevicePath = *hashDeviceAlias
+	}
+
+	// Print a DeviceMapper target table, or save it to a file, if the table
+	// parameter was specified.
+	if *table != "" {
+		if err := os.WriteFile(*table, []byte(mt.String()), 0644); err != nil {
+			log.Fatal(err)
+		}
+	} else {
+		fmt.Println(mt)
+	}
+}