blob: 7300f4920d11685048e8a1173436055bee40f102 [file] [log] [blame]
Mateusz Zalega356b8962021-08-10 17:27:15 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// This package implements a command line tool that creates dm-verity hash
18// images at a selected path, given an existing data image. The tool
19// outputs a Verity mapping table on success.
20//
21// For more information, see:
22// - source.monogon.dev/metropolis/pkg/verity
23// - https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
24package main
25
26import (
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010027 "flag"
Mateusz Zalega356b8962021-08-10 17:27:15 +020028 "fmt"
29 "io"
30 "log"
31 "os"
32
33 "source.monogon.dev/metropolis/pkg/verity"
34)
35
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010036// createImage creates a dm-verity target image by combining the input image
37// with Verity metadata. Contents of the data image are copied to the output
38// image. Then, the same contents are verity-encoded and appended to the
39// output image. The verity superblock is written only if wsb is true. It
40// returns either a dm-verity target table, or an error.
41func createImage(dataImagePath, outputImagePath string, wsb bool) (*verity.MappingTable, error) {
42 // Hardcode both the data block size and the hash block size as 4096 bytes.
43 bs := uint32(4096)
44
Mateusz Zalega356b8962021-08-10 17:27:15 +020045 // Open the data image for reading.
46 dataImage, err := os.Open(dataImagePath)
47 if err != nil {
48 return nil, fmt.Errorf("while opening the data image: %w", err)
49 }
50 defer dataImage.Close()
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010051
52 // Check that the data image is well-formed.
53 ds, err := dataImage.Stat()
54 if err != nil {
55 return nil, fmt.Errorf("while stat-ing the data image: %w", err)
56 }
57 if !ds.Mode().IsRegular() {
58 return nil, fmt.Errorf("the data image must be a regular file")
59 }
60 if ds.Size()%int64(bs) != 0 {
61 return nil, fmt.Errorf("the data image must end on a %d-byte block boundary.", bs)
62 }
63
Mateusz Zalega356b8962021-08-10 17:27:15 +020064 // Create an empty hash image file.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010065 outputImage, err := os.OpenFile(outputImagePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
Mateusz Zalega356b8962021-08-10 17:27:15 +020066 if err != nil {
67 return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
68 }
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010069 defer outputImage.Close()
Mateusz Zalega356b8962021-08-10 17:27:15 +020070
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010071 // Copy the input data into the output file, then rewind dataImage to be read
72 // again by the Verity encoder.
73 _, err = io.Copy(outputImage, dataImage)
74 if err != nil {
75 return nil, err
76 }
77 _, err = dataImage.Seek(0, os.SEEK_SET)
78 if err != nil {
79 return nil, err
80 }
81
82 // Write outputImage contents. Start with initializing a verity encoder,
83 // seting outputImage as its output.
84 v, err := verity.NewEncoder(outputImage, bs, bs, wsb)
Mateusz Zalega356b8962021-08-10 17:27:15 +020085 if err != nil {
86 return nil, fmt.Errorf("while initializing a verity encoder: %w", err)
87 }
88 // Hash the contents of dataImage, block by block.
89 _, err = io.Copy(v, dataImage)
90 if err != nil {
91 return nil, fmt.Errorf("while reading the data image: %w", err)
92 }
93 // The resulting hash tree won't be written until Close is called.
94 err = v.Close()
95 if err != nil {
96 return nil, fmt.Errorf("while writing the hash image: %w", err)
97 }
98
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +010099 // Return an encoder-generated verity mapping table, containing the salt and
100 // the root hash. First, calculate the starting hash block by dividing the
101 // data image size by the encoder data block size.
102 hashStart := ds.Size() / int64(bs)
103 mt, err := v.MappingTable(dataImagePath, outputImagePath, hashStart)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200104 if err != nil {
105 return nil, fmt.Errorf("while querying for the mapping table: %w", err)
106 }
107 return mt, nil
108}
109
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100110var (
111 input = flag.String("input", "", "input disk image (required)")
112 output = flag.String("output", "", "output disk image with Verity metadata appended (required)")
113 dataDeviceAlias = flag.String("data_alias", "", "data device alias used in the mapping table")
114 hashDeviceAlias = flag.String("hash_alias", "", "hash device alias used in the mapping table")
115 table = flag.String("table", "", "a file the mapping table will be saved to; disables stdout")
116)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200117
118func main() {
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100119 flag.Parse()
Mateusz Zalega356b8962021-08-10 17:27:15 +0200120
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100121 // Ensure that required parameters were provided before continuing.
122 if *input == "" {
123 log.Fatalf("-input must be set.")
124 }
125 if *output == "" {
126 log.Fatalf("-output must be set.")
127 }
128
129 // Build the image.
130 mt, err := createImage(*input, *output, false)
Mateusz Zalega356b8962021-08-10 17:27:15 +0200131 if err != nil {
132 log.Fatal(err)
133 }
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100134
135 // Patch the device names, if alternatives were provided.
136 if *dataDeviceAlias != "" {
137 mt.DataDevicePath = *dataDeviceAlias
138 }
139 if *hashDeviceAlias != "" {
140 mt.HashDevicePath = *hashDeviceAlias
141 }
142
143 // Print a DeviceMapper target table, or save it to a file, if the table
144 // parameter was specified.
145 if *table != "" {
146 if err := os.WriteFile(*table, []byte(mt.String()), 0644); err != nil {
147 log.Fatal(err)
148 }
149 } else {
150 fmt.Println(mt)
151 }
Mateusz Zalega356b8962021-08-10 17:27:15 +0200152}