1578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen/*
2578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * Copyright (C) 2014 The Android Open Source Project
3578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen *
4578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * Licensed under the Apache License, Version 2.0 (the "License");
5578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * you may not use this file except in compliance with the License.
6578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * You may obtain a copy of the License at
7578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen *
8578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen *      http://www.apache.org/licenses/LICENSE-2.0
9578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen *
10578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * Unless required by applicable law or agreed to in writing, software
11578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * distributed under the License is distributed on an "AS IS" BASIS,
12578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * See the License for the specific language governing permissions and
14578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen * limitations under the License.
15578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen */
16578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
17578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenpackage com.android.verity;
18578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
19578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.io.File;
20578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.io.RandomAccessFile;
21578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.nio.ByteBuffer;
22578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.nio.ByteOrder;
23578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.lang.Process;
24578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.lang.Runtime;
25578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.security.PublicKey;
26578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.security.PrivateKey;
27578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.security.Security;
28578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport java.security.cert.X509Certificate;
29578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenimport org.bouncycastle.jce.provider.BouncyCastleProvider;
30578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
31578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanenpublic class VerityVerifier {
32578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
33578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_MAGIC = 0xEF53;
34578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_OFFSET = 0x400;
35578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
36578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
37578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
38578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
39578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int VERITY_MAGIC = 0xB001B001;
40578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int VERITY_SIGNATURE_SIZE = 256;
41578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    private static final int VERITY_VERSION = 0;
42578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
43578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    /**
44578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * Converts a 4-byte little endian value to a Java integer
45578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param value Little endian integer to convert
46578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     */
47578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     public static int fromle(int value) {
48578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
49578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
50578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
51578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
52578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     /**
53578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * Converts a 2-byte little endian value to Java a integer
54578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param value Little endian short to convert
55578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     */
56578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     public static int fromle(short value) {
57578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        return fromle(value << 16);
58578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
59578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
60578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    /**
61578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * Unsparses a sparse image into a temporary file and returns a
62578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * handle to the file
63578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param fname Path to a sparse image file
64578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     */
65578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     public static RandomAccessFile openImage(String fname) throws Exception {
66578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        File tmp = File.createTempFile("system", ".raw");
67578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        tmp.deleteOnExit();
68578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
69578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        Process p = Runtime.getRuntime().exec("simg2img " + fname +
70578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen                            " " + tmp.getAbsoluteFile());
71578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
72578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        p.waitFor();
73578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        if (p.exitValue() != 0) {
74578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throw new IllegalArgumentException("Invalid image: failed to unsparse");
75578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
76578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
77578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        return new RandomAccessFile(tmp, "r");
78578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
79578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
80578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    /**
81578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * Reads the ext4 superblock and calculates the size of the system image,
82578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * after which we should find the verity metadata
83578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param img File handle to the image file
84578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     */
85578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    public static long getMetadataPosition(RandomAccessFile img)
86578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throws Exception {
87578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.seek(EXT4_SB_OFFSET_MAGIC);
88578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        int magic = fromle(img.readShort());
89578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
90578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        if (magic != EXT4_SB_MAGIC) {
91578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throw new IllegalArgumentException("Invalid image: not a valid ext4 image");
92578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
93578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
94578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO);
95578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        long blocksCountLo = fromle(img.readInt());
96578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
97578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE);
98578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        long logBlockSize = fromle(img.readInt());
99578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
100578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI);
101578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        long blocksCountHi = fromle(img.readInt());
102578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
103578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        long blockSizeBytes = 1L << (10 + logBlockSize);
104578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        long blockCount = (blocksCountHi << 32) + blocksCountLo;
105578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        return blockSizeBytes * blockCount;
106578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
107578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
108578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    /**
109578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * Reads and validates verity metadata, and check the signature against the
110578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * given public key
111578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param img File handle to the image file
112578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     * @param key Public key to use for signature verification
113578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen     */
114578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
115578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throws Exception {
116578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.seek(getMetadataPosition(img));
117578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        int magic = fromle(img.readInt());
118578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
119578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        if (magic != VERITY_MAGIC) {
120578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throw new IllegalArgumentException("Invalid image: verity metadata not found");
121578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
122578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
123578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        int version = fromle(img.readInt());
124578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
125578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        if (version != VERITY_VERSION) {
126578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            throw new IllegalArgumentException("Invalid image: unknown metadata version");
127578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
128578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
129578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
130578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.readFully(signature);
131578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
132578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        int tableSize = fromle(img.readInt());
133578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
134578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        byte[] table = new byte[tableSize];
135578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        img.readFully(table);
136578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
137578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        return Utils.verify(key, table, signature,
138578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen                   Utils.getSignatureAlgorithmIdentifier(key));
139578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
140578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
141578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    public static void main(String[] args) throws Exception {
142578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        if (args.length != 2) {
143578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
144578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            System.exit(1);
145578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
146578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
147578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        Security.addProvider(new BouncyCastleProvider());
148578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
149578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        X509Certificate cert = Utils.loadPEMCertificate(args[1]);
150578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        PublicKey key = cert.getPublicKey();
151578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        RandomAccessFile img = openImage(args[0]);
152578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
153578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        try {
154578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            if (verifyMetaData(img, key)) {
155578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen                System.err.println("Signature is VALID");
156578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen                System.exit(0);
157578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            } else {
158578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen                System.err.println("Signature is INVALID");
159578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            }
160578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        } catch (Exception e) {
161578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen            e.printStackTrace(System.err);
162578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        }
163578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen
164578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen        System.exit(1);
165578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen    }
166578a347c1939b62805442ff3a769092b19c42db2Sami Tolvanen}
167