m/n/b/mkverity: refactor into VerityEncoder

The implementation was refactored into a stream-oriented VerityEncoder and exposed for use outside the mkverity tool. In addition, end-to-end tests were provided.

Change-Id: I2d009ca8030d6a86e9d6dbe6d6ae60a3b84d2d74
Reviewed-on: https://review.monogon.dev/c/monogon/+/314
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/mkverity/mkverity.go b/metropolis/node/build/mkverity/mkverity.go
new file mode 100644
index 0000000..272bc73
--- /dev/null
+++ b/metropolis/node/build/mkverity/mkverity.go
@@ -0,0 +1,101 @@
+// 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/metropolis/pkg/verity
+// - https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
+package main
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"os"
+
+	"source.monogon.dev/metropolis/pkg/verity"
+)
+
+// createHashImage creates a complete dm-verity hash image at
+// hashImagePath. Contents of the file at dataImagePath are accessed
+// read-only, hashed and written to the hash image in the process.
+// The verity superblock is written only if wsb is true.
+// It returns a string-convertible VerityMappingTable, or an error.
+func createHashImage(dataImagePath, hashImagePath string, wsb bool) (*verity.MappingTable, error) {
+	// 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()
+	// Create an empty hash image file.
+	hashImage, err := os.OpenFile(hashImagePath, os.O_RDWR|os.O_CREATE, 0644)
+	if err != nil {
+		return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
+	}
+	defer hashImage.Close()
+
+	// Write hashImage contents. Start with initializing a verity encoder,
+	// seting hashImage as its output.
+	v, err := verity.NewEncoder(hashImage, 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.
+	mt, err := v.MappingTable(dataImagePath, hashImagePath)
+	if err != nil {
+		return nil, fmt.Errorf("while querying for the mapping table: %w", err)
+	}
+	return mt, nil
+}
+
+// usage prints program usage information.
+func usage(executable string) {
+	fmt.Println("Usage: ", executable, " <data image> <hash image>")
+}
+
+func main() {
+	if len(os.Args) != 3 {
+		usage(os.Args[0])
+		os.Exit(2)
+	}
+	dataImagePath := os.Args[1]
+	hashImagePath := os.Args[2]
+
+	// Attempt to build a new Verity hash Image at hashImagePath, based on
+	// the data image at dataImagePath. Include the Verity superblock.
+	mt, err := createHashImage(dataImagePath, hashImagePath, true)
+	if err != nil {
+		log.Fatal(err)
+	}
+	// Print a Device Mapper compatible mapping table.
+	fmt.Println(mt)
+}