blob: d31e6479390a9f827a0f6a2d7135d76a30b4c9e1 [file] [log] [blame]
Mateusz Zalega14453962021-07-23 16:58:02 +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 subset of the veritysetup tool from cryptsetup,
18// which is a userland tool to interact with dm-verity devices/images. It was
19// rewritten to provide the minimum of functionality needed for Metropolis
20// without having to package, link against and maintain the original C
21// veritysetup tool.
22//
23// dm-verity is a Linux device mapper target that allows integrity verification of
24// a read-only block device. The block device whose integrity should be checked
25// (the 'data device') must be first processed by a tool like veritysetup (or this
26// tool, mkverity) to generate a hash device and root hash.
27// The original data device, hash device and root hash are then set up as a device
28// mapper target, and any read performed from the data device through the verity
29// target will be verified for integrity by Linux using the hash device and root
30// hash.
31//
32// Internally, the hash device is a Merkle tree of all the bytes in the data
33// device, layed out as layers of 'hash blocks'. Starting with data bytes, layers
34// are built recursively, with each layer's output hash blocks becoming the next
35// layer's data input, ending with the single root hash.
36//
37// For more information about the internals, see the Linux and cryptsetup
38// upstream code:
39//
40// https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#verity-superblock-format
41package main
42
43import (
44 "bytes"
45 "crypto/rand"
46 "crypto/sha256"
47 "encoding/binary"
48 "fmt"
49 "io"
50 "log"
51 "os"
52)
53
54// veritySuperblock represents data layout inside of a dm-verity hash block
55// device superblock. It follows a preexisting verity implementation:
56//
57// https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#verity-superblock-format
58type veritySuperblock struct {
59 // signature is the magic signature of a verity hash device superblock,
60 // "verity\0\0".
61 signature [8]uint8
62 // version specifies a superblock format. This structure describes version
63 // '1'.
64 version uint32
65 // hashType defaults to '1' outside Chrome OS, according to scarce dm-verity
66 // documentation.
67 hashType uint32
68 // uuid contains a UUID of the hash device.
69 uuid [16]uint8
70 // algorithm stores an ASCII-encoded name of the hash function used.
71 algorithm [32]uint8
72
73 // dataBlockSize specifies a size of a single data device block, in bytes.
74 dataBlockSize uint32
75 // hashBlockSize specifies a size of a single hash device block, in bytes.
76 hashBlockSize uint32
77 // dataBlocks contains a count of blocks available on the data device.
78 dataBlocks uint64
79
80 // saltSize encodes the size of hash block salt, up to the maximum of 256 bytes.
81 saltSize uint16
82
83 // _pad1 is a zeroed space prepending the salt; unused.
84 _pad1 [6]uint8
85 // exactly saltSize bytes of salt are prepended to data blocks before hashing.
86 salt [256]uint8
87 // _pad2 is a zeroed space after the salt; unused.
88 _pad2 [168]uint8
89}
90
91// divideAndRoundup performs an integer division and returns a rounded up
92// result. Useful in calculating block counts.
93func divideAndRoundup(a, b uint64) uint64 {
94 r := a / b
95 if a%b != 0 {
96 r++
97 }
98 return r
99}
100
101// newSuperblock builds a dm-verity hash device superblock based on the size
102// of data image file reachable through dataImagePath.
103// It returns either a fully initialized veritySuperblock, or an
104// initialization error.
105func newSuperblock(dataImagePath string) (*veritySuperblock, error) {
106 // This implementation only handles SHA256-based verity hash images
107 // with a specific 4096-byte block size.
108 // Block sizes can be updated by adjusting the struct literal below.
109 // A change of a hashing algorithm would require a refactor of
110 // saltedDigest, and references to sha256.Size.
111 //
112 // Fill in the defaults (compare with veritySuperblock definition).
113 sb := veritySuperblock{
114 signature: [8]uint8{'v', 'e', 'r', 'i', 't', 'y', 0, 0},
115 version: 1,
116 hashType: 1,
117 algorithm: [32]uint8{'s', 'h', 'a', '2', '5', '6'},
118 saltSize: 256,
119 dataBlockSize: 4096,
120 hashBlockSize: 4096,
121 }
122
123 // Get the data image size and compute the data block count.
124 ds, err := os.Stat(dataImagePath)
125 if err != nil {
126 return nil, fmt.Errorf("while stat-ing data device: %w", err)
127 }
128 if !ds.Mode().IsRegular() {
129 return nil, fmt.Errorf("this program only accepts regular files")
130 }
131 sb.dataBlocks = divideAndRoundup(uint64(ds.Size()), uint64(sb.dataBlockSize))
132
133 // Fill in the superblock UUID and cryptographic salt.
134 if _, err := rand.Read(sb.uuid[:]); err != nil {
135 return nil, fmt.Errorf("when generating UUID: %w", err)
136 }
137 if _, err := rand.Read(sb.salt[:]); err != nil {
138 return nil, fmt.Errorf("when generating salt: %w", err)
139 }
140
141 return &sb, nil
142}
143
144// saltedDigest computes and returns a SHA256 sum of a block prepended
145// with a Superblock-defined salt.
146func (sb *veritySuperblock) saltedDigest(data []byte) (digest [sha256.Size]byte) {
147 h := sha256.New()
148 h.Write(sb.salt[:int(sb.saltSize)])
149 h.Write(data)
150 copy(digest[:], h.Sum(nil))
151 return
152}
153
154// dataBlocksPerHashBlock returns the count of hash operation outputs that
155// fit in a hash device block. This is also the amount of data device
156// blocks it takes to populate a hash device block.
157func (sb *veritySuperblock) dataBlocksPerHashBlock() uint64 {
158 return uint64(sb.hashBlockSize) / sha256.Size
159}
160
161// computeHashBlock reads at most sb.dataBlocksPerHashBlock blocks from
162// the given reader object, returning a padded hash block of length
163// defined by sb.hashBlockSize, and an error, if encountered.
164// In case a non-nil block is returned, it's guaranteed to contain at
165// least one hash. An io.EOF signals that there is no more to be read
166// from 'r'.
167func (sb *veritySuperblock) computeHashBlock(r io.Reader) ([]byte, error) {
168 // Preallocate a whole hash block.
169 hblk := bytes.NewBuffer(make([]byte, 0, sb.hashBlockSize))
170
171 // For every data block, compute a hash and place it in hblk. Continue
172 // till EOF.
173 for b := uint64(0); b < sb.dataBlocksPerHashBlock(); b++ {
174 dbuf := make([]byte, sb.dataBlockSize)
175 // Attempt to read enough data blocks to make a complete hash block.
176 n, err := io.ReadFull(r, dbuf)
177 // If any data was read, make a hash and add it to the hash buffer.
178 if n != 0 {
179 hash := sb.saltedDigest(dbuf)
180 hblk.Write(hash[:])
181 }
182 // Handle the read errors.
183 switch err {
184 case nil:
185 case io.ErrUnexpectedEOF, io.EOF:
186 // io.ReadFull returns io.ErrUnexpectedEOF after a partial read,
187 // and io.EOF if no bytes were read. In both cases it's possible
188 // to end up with a partially filled hash block.
189 if hblk.Len() != 0 {
190 // Return a zero-padded hash block if any hashes were written
191 // to it, and signal that no more blocks can be built.
192 res := hblk.Bytes()
193 return res[:cap(res)], io.EOF
194 }
195 // Return nil if the block doesn't contain any hashes.
196 return nil, io.EOF
197 default:
198 // Wrap unhandled read errors.
199 return nil, fmt.Errorf("while computing a hash block: %w", err)
200 }
201 }
202 // Return a completely filled hash block.
203 res := hblk.Bytes()
204 return res[:cap(res)], nil
205}
206
207// writeSuperblock writes a verity superblock to a given writer object.
208// It returns a write error, if encountered.
209func (sb *veritySuperblock) writeSuperblock(w io.Writer) error {
210 // Write the superblock.
211 if err := binary.Write(w, binary.LittleEndian, sb); err != nil {
212 return fmt.Errorf("while writing a header: %w", err)
213 }
214
215 // Get the padding size by substracting current offset from a hash block
216 // size.
217 co := binary.Size(sb)
218 pbc := int(sb.hashBlockSize) - co
219 if pbc <= 0 {
220 return fmt.Errorf("hash device block size smaller than dm-verity superblock")
221 }
222
223 // Write the padding bytes at the end of the block.
224 if _, err := w.Write(bytes.Repeat([]byte{0}, pbc)); err != nil {
225 return fmt.Errorf("while writing padding: %w", err)
226 }
227 return nil
228}
229
230// computeLevelZero produces the base level of a hash tree. It's the only
231// level calculated based on raw input from the data image.
232// It returns a byte slice containing one or more hash blocks, depending
233// on sb.dataBlocks and sb.hashBlockSize, or an error. The returned slice
234// length is guaranteed to be a multiple of sb.hashBlockSize if no error
235// is returned.
236// BUG(mz): Current implementation requires a 1/128th of the data image
237// size to be allocatable on the heap.
238func (sb *veritySuperblock) computeLevel(r io.Reader) ([]byte, error) {
239 // hbuf will store all the computed hash blocks.
240 var hbuf bytes.Buffer
241 // Compute one or more hash blocks, reading all data available in the
242 // 'r' reader object, and write them into hbuf.
243 for {
244 hblk, err := sb.computeHashBlock(r)
245 if err != nil && err != io.EOF {
246 return nil, fmt.Errorf("while building a hash tree level: %w", err)
247 }
248 if hblk != nil {
249 _, err := hbuf.Write(hblk)
250 if err != nil {
251 return nil, fmt.Errorf("while writing to hash block buffer: %w", err)
252 }
253 }
254 if err == io.EOF {
255 break
256 }
257 }
258 return hbuf.Bytes(), nil
259}
260
261// computeHashTree builds a complete hash tree based on the given reader
262// object. Levels are appended to resulting hashTree from bottom to top.
263// It returns a verity hash tree, a verity root hash, and an error, if
264// encountered.
265func (sb *veritySuperblock) computeHashTree(r io.Reader) ([][]byte, []byte, error) {
266 // First, hash contents of the data image. This will result in a bottom
267 // level of the hash tree.
268 var hashTree [][]byte
269 lz, err := sb.computeLevel(r)
270 if err != nil {
271 return nil, nil, fmt.Errorf("while computing the base level: %w", err)
272 }
273 hashTree = append(hashTree, lz)
274
275 // Other levels are built by hashing the hash blocks comprising a level
276 // below.
277 for {
278 // Create the next level by hashing the previous one.
279 pl := hashTree[len(hashTree)-1]
280 nl, err := sb.computeLevel(bytes.NewReader(pl))
281 if err != nil {
282 return nil, nil, fmt.Errorf("while computing a level: %w", err)
283 }
284 // Append the resulting next level to a tree.
285 hashTree = append(hashTree, nl)
286
287 if len(nl) == int(sb.hashBlockSize) {
288 // The last level to compute has a size of exactly one hash block.
289 // That's the root level. Its hash serves as a cryptographic root of
290 // trust and is returned separately.
291 rootHash := sb.saltedDigest(nl)
292 return hashTree, rootHash[:], nil
293 }
294 }
295}
296
297// writeHashTree writes a verity-formatted hash tree to the given writer
298// object. Compare with computeHashTree.
299// It returns the count of bytes written and a write error, if encountered.
300func (sb *veritySuperblock) writeHashTree(w io.Writer, treeLevels [][]byte) error {
301 // Write the hash tree levels from top to bottom.
302 for l := len(treeLevels) - 1; l >= 0; l-- {
303 level := treeLevels[l]
304 // Call w.Write until a whole level is written.
305 for len(level) != 0 {
306 n, err := w.Write(level)
307 if err != nil && err != io.ErrShortWrite {
308 return fmt.Errorf("while writing a level: %w", err)
309 }
310 level = level[n:]
311 }
312 }
313 return nil
314}
315
316// createHashImage creates a complete dm-verity hash image at
317// hashImagePath. Contents of the file at dataImagePath are accessed
318// read-only, hashed and written to the hash image in the process.
319// It returns a verity root hash and an error, if encountered.
320func createHashImage(dataImagePath, hashImagePath string) ([]byte, error) {
321 // Inspect the data image and build a verity superblock based on its size.
322 sb, err := newSuperblock(dataImagePath)
323 if err != nil {
324 return nil, fmt.Errorf("while building a superblock: %w", err)
325 }
326
327 // Open the data image for reading.
328 dataImage, err := os.Open(dataImagePath)
329 if err != nil {
330 return nil, fmt.Errorf("while opening the data image: %w", err)
331 }
332 defer dataImage.Close()
333
334 // Create an empty hash image file.
335 hashImage, err := os.OpenFile(hashImagePath, os.O_RDWR|os.O_CREATE, 0644)
336 if err != nil {
337 return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
338 }
339 defer hashImage.Close()
340
341 // Write the superblock to the hash image.
342 if err = sb.writeSuperblock(hashImage); err != nil {
343 return nil, fmt.Errorf("while writing the superblock: %w", err)
344 }
345
346 // Compute a verity hash tree by hashing contents of the data image. Then,
347 // write it to the hash image.
348 treeLevels, rootHash, err := sb.computeHashTree(dataImage)
349 if err != nil {
350 return nil, fmt.Errorf("while building a hash tree: %w", err)
351 }
352 if err = sb.writeHashTree(hashImage, treeLevels); err != nil {
353 return nil, fmt.Errorf("while writing a hash tree: %w", err)
354 }
355
356 // Return a verity root hash, serving as a root of trust.
357 return rootHash, nil
358}
359
360// usage prints program usage information.
361func usage(executable string) {
362 fmt.Println("Usage: ", executable, " format <data image> <hash image>")
363}
364
365func main() {
366 // Process the command line arguments maintaining a partial
367 // compatibility with veritysetup.
368 if len(os.Args) != 4 {
369 usage(os.Args[0])
370 os.Exit(2)
371 }
372 command := os.Args[1]
373 dataImagePath := os.Args[2]
374 hashImagePath := os.Args[3]
375
376 switch command {
377 case "format":
378 rootHash, err := createHashImage(dataImagePath, hashImagePath)
379 if err != nil {
380 log.Fatal(err)
381 }
382 // The output differs from the original veritysetup utility in that hash
383 // isn't prepended by "Root hash: " string. It's left this way to
384 // facilitate machine processing.
385 fmt.Printf("%x", rootHash)
386 default:
387 usage(os.Args[0])
388 os.Exit(2)
389 }
390}