|  | // 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) | 
|  | } | 
|  | } |