| // 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 ( | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"log" | 
 | 	"os" | 
 |  | 
 | 	"source.monogon.dev/metropolis/pkg/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) | 
 | 	} | 
 | } |