blob: 7300f4920d11685048e8a1173436055bee40f102 [file] [log] [blame]
// 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)
}
}