blob: f75845d25ffcf41755848ff4b6043dfef5414d02 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Mateusz Zalega356b8962021-08-10 17:27:15 +02002// SPDX-License-Identifier: Apache-2.0
Mateusz Zalega356b8962021-08-10 17:27:15 +02003
4// This package implements a command line tool that creates dm-verity hash
5// images at a selected path, given an existing data image. The tool
6// outputs a Verity mapping table on success.
7//
8// For more information, see:
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02009// - source.monogon.dev/osbase/verity
Mateusz Zalega356b8962021-08-10 17:27:15 +020010// - https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
11package main
12
13import (
Jan Schär3871fa12025-07-09 17:30:00 +000014 "crypto/sha256"
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010015 "flag"
Mateusz Zalega356b8962021-08-10 17:27:15 +020016 "fmt"
17 "io"
18 "log"
19 "os"
20
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020021 "source.monogon.dev/osbase/verity"
Mateusz Zalega356b8962021-08-10 17:27:15 +020022)
23
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010024// createImage creates a dm-verity target image by combining the input image
25// with Verity metadata. Contents of the data image are copied to the output
26// image. Then, the same contents are verity-encoded and appended to the
27// output image. The verity superblock is written only if wsb is true. It
28// returns either a dm-verity target table, or an error.
29func createImage(dataImagePath, outputImagePath string, wsb bool) (*verity.MappingTable, error) {
30 // Hardcode both the data block size and the hash block size as 4096 bytes.
31 bs := uint32(4096)
32
Mateusz Zalega356b8962021-08-10 17:27:15 +020033 // Open the data image for reading.
34 dataImage, err := os.Open(dataImagePath)
35 if err != nil {
36 return nil, fmt.Errorf("while opening the data image: %w", err)
37 }
38 defer dataImage.Close()
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010039
40 // Check that the data image is well-formed.
41 ds, err := dataImage.Stat()
42 if err != nil {
43 return nil, fmt.Errorf("while stat-ing the data image: %w", err)
44 }
45 if !ds.Mode().IsRegular() {
46 return nil, fmt.Errorf("the data image must be a regular file")
47 }
48 if ds.Size()%int64(bs) != 0 {
Tim Windelschmidt73e98822024-04-18 23:13:49 +020049 return nil, fmt.Errorf("the data image must end on a %d-byte block boundary", bs)
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010050 }
51
Mateusz Zalega356b8962021-08-10 17:27:15 +020052 // Create an empty hash image file.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010053 outputImage, err := os.OpenFile(outputImagePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
Mateusz Zalega356b8962021-08-10 17:27:15 +020054 if err != nil {
55 return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
56 }
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010057 defer outputImage.Close()
Mateusz Zalega356b8962021-08-10 17:27:15 +020058
Jan Schär3871fa12025-07-09 17:30:00 +000059 // Copy the input data into the output file, then rewind dataImage.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010060 _, err = io.Copy(outputImage, dataImage)
61 if err != nil {
62 return nil, err
63 }
Jan Schär3871fa12025-07-09 17:30:00 +000064 _, err = dataImage.Seek(0, io.SeekStart)
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010065 if err != nil {
66 return nil, err
67 }
68
Jan Schär3871fa12025-07-09 17:30:00 +000069 // Hash the input data for use as the salt, then rewind dataImage. The purpose
70 // of the salt is to prevent reuse of collisions across different images. 16
71 // bytes is enough for this. We use a hash of the input instead of generating
72 // random bytes to make the build reproducible.
73 dataHash := sha256.New()
74 _, err = io.Copy(dataHash, dataImage)
75 if err != nil {
76 return nil, err
77 }
78 _, err = dataImage.Seek(0, io.SeekStart)
79 if err != nil {
80 return nil, err
81 }
82 salt := dataHash.Sum(nil)[:16]
83
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010084 // Write outputImage contents. Start with initializing a verity encoder,
Jan Schär3871fa12025-07-09 17:30:00 +000085 // setting outputImage as its output.
86 v, err := verity.NewEncoder(outputImage, bs, bs, salt, wsb)
Mateusz Zalega356b8962021-08-10 17:27:15 +020087 if err != nil {
88 return nil, fmt.Errorf("while initializing a verity encoder: %w", err)
89 }
90 // Hash the contents of dataImage, block by block.
91 _, err = io.Copy(v, dataImage)
92 if err != nil {
93 return nil, fmt.Errorf("while reading the data image: %w", err)
94 }
95 // The resulting hash tree won't be written until Close is called.
96 err = v.Close()
97 if err != nil {
98 return nil, fmt.Errorf("while writing the hash image: %w", err)
99 }
100
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100101 // Return an encoder-generated verity mapping table, containing the salt and
102 // the root hash. First, calculate the starting hash block by dividing the
103 // data image size by the encoder data block size.
104 hashStart := ds.Size() / int64(bs)
105 mt, err := v.MappingTable(dataImagePath, outputImagePath, hashStart)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200106 if err != nil {
107 return nil, fmt.Errorf("while querying for the mapping table: %w", err)
108 }
109 return mt, nil
110}
111
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100112var (
113 input = flag.String("input", "", "input disk image (required)")
114 output = flag.String("output", "", "output disk image with Verity metadata appended (required)")
115 dataDeviceAlias = flag.String("data_alias", "", "data device alias used in the mapping table")
116 hashDeviceAlias = flag.String("hash_alias", "", "hash device alias used in the mapping table")
117 table = flag.String("table", "", "a file the mapping table will be saved to; disables stdout")
118)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200119
120func main() {
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100121 flag.Parse()
Mateusz Zalega356b8962021-08-10 17:27:15 +0200122
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100123 // Ensure that required parameters were provided before continuing.
124 if *input == "" {
125 log.Fatalf("-input must be set.")
126 }
127 if *output == "" {
128 log.Fatalf("-output must be set.")
129 }
130
131 // Build the image.
132 mt, err := createImage(*input, *output, false)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200133 if err != nil {
134 log.Fatal(err)
135 }
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100136
137 // Patch the device names, if alternatives were provided.
138 if *dataDeviceAlias != "" {
139 mt.DataDevicePath = *dataDeviceAlias
140 }
141 if *hashDeviceAlias != "" {
142 mt.HashDevicePath = *hashDeviceAlias
143 }
144
145 // Print a DeviceMapper target table, or save it to a file, if the table
146 // parameter was specified.
147 if *table != "" {
148 if err := os.WriteFile(*table, []byte(mt.String()), 0644); err != nil {
149 log.Fatal(err)
150 }
151 } else {
152 fmt.Println(mt)
153 }
Mateusz Zalega356b8962021-08-10 17:27:15 +0200154}