blob: 5c9d7d28340e656a4025541c59b86c6259c99d77 [file] [log] [blame]
Sami Tolvanenc18aa9d2014-11-12 07:47:53 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.verity;
18
19import java.io.File;
20import java.io.RandomAccessFile;
21import java.nio.ByteBuffer;
22import java.nio.ByteOrder;
23import java.lang.Process;
24import java.lang.Runtime;
25import java.security.PublicKey;
26import java.security.PrivateKey;
27import java.security.Security;
28import java.security.cert.X509Certificate;
29import org.bouncycastle.jce.provider.BouncyCastleProvider;
30
31public class VerityVerifier {
32
33 private static final int EXT4_SB_MAGIC = 0xEF53;
34 private static final int EXT4_SB_OFFSET = 0x400;
35 private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
36 private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
37 private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
38 private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
39 private static final int VERITY_MAGIC = 0xB001B001;
40 private static final int VERITY_SIGNATURE_SIZE = 256;
41 private static final int VERITY_VERSION = 0;
42
43 /**
44 * Converts a 4-byte little endian value to a Java integer
45 * @param value Little endian integer to convert
46 */
47 public static int fromle(int value) {
48 byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
49 return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
50 }
51
52 /**
53 * Converts a 2-byte little endian value to Java a integer
54 * @param value Little endian short to convert
55 */
56 public static int fromle(short value) {
57 return fromle(value << 16);
58 }
59
60 /**
61 * Unsparses a sparse image into a temporary file and returns a
62 * handle to the file
63 * @param fname Path to a sparse image file
64 */
65 public static RandomAccessFile openImage(String fname) throws Exception {
66 File tmp = File.createTempFile("system", ".raw");
67 tmp.deleteOnExit();
68
69 Process p = Runtime.getRuntime().exec("simg2img " + fname +
70 " " + tmp.getAbsoluteFile());
71
72 p.waitFor();
73 if (p.exitValue() != 0) {
74 throw new IllegalArgumentException("Invalid image: failed to unsparse");
75 }
76
77 return new RandomAccessFile(tmp, "r");
78 }
79
80 /**
81 * Reads the ext4 superblock and calculates the size of the system image,
82 * after which we should find the verity metadata
83 * @param img File handle to the image file
84 */
85 public static long getMetadataPosition(RandomAccessFile img)
86 throws Exception {
87 img.seek(EXT4_SB_OFFSET_MAGIC);
88 int magic = fromle(img.readShort());
89
90 if (magic != EXT4_SB_MAGIC) {
91 throw new IllegalArgumentException("Invalid image: not a valid ext4 image");
92 }
93
94 img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO);
95 long blocksCountLo = fromle(img.readInt());
96
97 img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE);
98 long logBlockSize = fromle(img.readInt());
99
100 img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI);
101 long blocksCountHi = fromle(img.readInt());
102
103 long blockSizeBytes = 1L << (10 + logBlockSize);
104 long blockCount = (blocksCountHi << 32) + blocksCountLo;
105 return blockSizeBytes * blockCount;
106 }
107
108 /**
109 * Reads and validates verity metadata, and check the signature against the
110 * given public key
111 * @param img File handle to the image file
112 * @param key Public key to use for signature verification
113 */
114 public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
115 throws Exception {
116 img.seek(getMetadataPosition(img));
117 int magic = fromle(img.readInt());
118
119 if (magic != VERITY_MAGIC) {
120 throw new IllegalArgumentException("Invalid image: verity metadata not found");
121 }
122
123 int version = fromle(img.readInt());
124
125 if (version != VERITY_VERSION) {
126 throw new IllegalArgumentException("Invalid image: unknown metadata version");
127 }
128
129 byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
130 img.readFully(signature);
131
132 int tableSize = fromle(img.readInt());
133
134 byte[] table = new byte[tableSize];
135 img.readFully(table);
136
137 return Utils.verify(key, table, signature,
138 Utils.getSignatureAlgorithmIdentifier(key));
139 }
140
141 public static void main(String[] args) throws Exception {
142 if (args.length != 2) {
143 System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
144 System.exit(1);
145 }
146
147 Security.addProvider(new BouncyCastleProvider());
148
149 X509Certificate cert = Utils.loadPEMCertificate(args[1]);
150 PublicKey key = cert.getPublicKey();
151 RandomAccessFile img = openImage(args[0]);
152
153 try {
154 if (verifyMetaData(img, key)) {
155 System.err.println("Signature is VALID");
156 System.exit(0);
157 } else {
158 System.err.println("Signature is INVALID");
159 }
160 } catch (Exception e) {
161 e.printStackTrace(System.err);
162 }
163
164 System.exit(1);
165 }
166}