1 /* 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 17 package com.android.verity; 18 19 import java.io.File; 20 import java.io.RandomAccessFile; 21 import java.nio.ByteBuffer; 22 import java.nio.ByteOrder; 23 import java.lang.Process; 24 import java.lang.Runtime; 25 import java.security.PublicKey; 26 import java.security.PrivateKey; 27 import java.security.Security; 28 import java.security.cert.X509Certificate; 29 import org.bouncycastle.jce.provider.BouncyCastleProvider; 30 31 public 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 } 167