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