diff options
Diffstat (limited to 'verity/VerityVerifier.java')
-rw-r--r-- | verity/VerityVerifier.java | 419 |
1 files changed, 0 insertions, 419 deletions
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java deleted file mode 100644 index 6b3f49ed..00000000 --- a/verity/VerityVerifier.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.verity; - -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.lang.Math; -import java.lang.Process; -import java.lang.Runtime; -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.MessageDigest; -import java.security.PublicKey; -import java.security.Security; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.RSAPublicKeySpec; -import java.util.ArrayList; -import java.util.Arrays; -import javax.xml.bind.DatatypeConverter; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -public class VerityVerifier { - - private ArrayList<Integer> hashBlocksLevel; - private byte[] hashTree; - private byte[] rootHash; - private byte[] salt; - private byte[] signature; - private byte[] table; - private File image; - private int blockSize; - private int hashBlockSize; - private int hashOffsetForData; - private int hashSize; - private int hashTreeSize; - private long hashStart; - private long imageSize; - private MessageDigest digest; - - private static final int EXT4_SB_MAGIC = 0xEF53; - private static final int EXT4_SB_OFFSET = 0x400; - private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38; - private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18; - private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4; - private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150; - private static final int MINCRYPT_OFFSET_MODULUS = 0x8; - private static final int MINCRYPT_OFFSET_EXPONENT = 0x208; - private static final int MINCRYPT_MODULUS_SIZE = 0x100; - private static final int MINCRYPT_EXPONENT_SIZE = 0x4; - private static final int VERITY_FIELDS = 10; - private static final int VERITY_MAGIC = 0xB001B001; - private static final int VERITY_SIGNATURE_SIZE = 256; - private static final int VERITY_VERSION = 0; - - public VerityVerifier(String fname) throws Exception { - digest = MessageDigest.getInstance("SHA-256"); - hashSize = digest.getDigestLength(); - hashBlocksLevel = new ArrayList<Integer>(); - hashTreeSize = -1; - openImage(fname); - readVerityData(); - } - - /** - * Reverses the order of bytes in a byte array - * @param value Byte array to reverse - */ - private static byte[] reverse(byte[] value) { - for (int i = 0; i < value.length / 2; i++) { - byte tmp = value[i]; - value[i] = value[value.length - i - 1]; - value[value.length - i - 1] = tmp; - } - - return value; - } - - /** - * Converts a 4-byte little endian value to a Java integer - * @param value Little endian integer to convert - */ - private static int fromle(int value) { - byte[] bytes = ByteBuffer.allocate(4).putInt(value).array(); - return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); - } - - /** - * Converts a 2-byte little endian value to Java a integer - * @param value Little endian short to convert - */ - private static int fromle(short value) { - return fromle(value << 16); - } - - /** - * Reads a 2048-bit RSA public key saved in mincrypt format, and returns - * a Java PublicKey for it. - * @param fname Name of the mincrypt public key file - */ - private static PublicKey getMincryptPublicKey(String fname) throws Exception { - try (RandomAccessFile key = new RandomAccessFile(fname, "r")) { - byte[] binaryMod = new byte[MINCRYPT_MODULUS_SIZE]; - byte[] binaryExp = new byte[MINCRYPT_EXPONENT_SIZE]; - - key.seek(MINCRYPT_OFFSET_MODULUS); - key.readFully(binaryMod); - - key.seek(MINCRYPT_OFFSET_EXPONENT); - key.readFully(binaryExp); - - BigInteger modulus = new BigInteger(1, reverse(binaryMod)); - BigInteger exponent = new BigInteger(1, reverse(binaryExp)); - - RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); - KeyFactory factory = KeyFactory.getInstance("RSA"); - return factory.generatePublic(spec); - } - } - - /** - * Unsparses a sparse image into a temporary file and returns a - * handle to the file - * @param fname Path to a sparse image file - */ - private void openImage(String fname) throws Exception { - image = File.createTempFile("system", ".raw"); - image.deleteOnExit(); - - Process p = Runtime.getRuntime().exec("simg2img " + fname + - " " + image.getAbsoluteFile()); - - p.waitFor(); - if (p.exitValue() != 0) { - throw new IllegalArgumentException("Invalid image: failed to unsparse"); - } - } - - /** - * Reads the ext4 superblock and calculates the size of the system image, - * after which we should find the verity metadata - * @param img File handle to the image file - */ - public static long getMetadataPosition(RandomAccessFile img) - throws Exception { - img.seek(EXT4_SB_OFFSET_MAGIC); - int magic = fromle(img.readShort()); - - if (magic != EXT4_SB_MAGIC) { - throw new IllegalArgumentException("Invalid image: not a valid ext4 image"); - } - - img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO); - long blocksCountLo = fromle(img.readInt()); - - img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE); - long logBlockSize = fromle(img.readInt()); - - img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI); - long blocksCountHi = fromle(img.readInt()); - - long blockSizeBytes = 1L << (10 + logBlockSize); - long blockCount = (blocksCountHi << 32) + blocksCountLo; - return blockSizeBytes * blockCount; - } - - /** - * Calculates the size of the verity hash tree based on the image size - */ - private int calculateHashTreeSize() { - if (hashTreeSize > 0) { - return hashTreeSize; - } - - int totalBlocks = 0; - int hashes = (int) (imageSize / blockSize); - - hashBlocksLevel.clear(); - - do { - hashBlocksLevel.add(0, hashes); - - int hashBlocks = - (int) Math.ceil((double) hashes * hashSize / hashBlockSize); - - totalBlocks += hashBlocks; - - hashes = hashBlocks; - } while (hashes > 1); - - hashTreeSize = totalBlocks * hashBlockSize; - return hashTreeSize; - } - - /** - * Parses the verity mapping table and reads the hash tree from - * the image file - * @param img Handle to the image file - * @param table Verity mapping table - */ - private void readHashTree(RandomAccessFile img, byte[] table) - throws Exception { - String tableStr = new String(table); - String[] fields = tableStr.split(" "); - - if (fields.length != VERITY_FIELDS) { - throw new IllegalArgumentException("Invalid image: unexpected number of fields " - + "in verity mapping table (" + fields.length + ")"); - } - - String hashVersion = fields[0]; - - if (!"1".equals(hashVersion)) { - throw new IllegalArgumentException("Invalid image: unsupported hash format"); - } - - String alg = fields[7]; - - if (!"sha256".equals(alg)) { - throw new IllegalArgumentException("Invalid image: unsupported hash algorithm"); - } - - blockSize = Integer.parseInt(fields[3]); - hashBlockSize = Integer.parseInt(fields[4]); - - int blocks = Integer.parseInt(fields[5]); - int start = Integer.parseInt(fields[6]); - - if (imageSize != (long) blocks * blockSize) { - throw new IllegalArgumentException("Invalid image: size mismatch in mapping " - + "table"); - } - - rootHash = DatatypeConverter.parseHexBinary(fields[8]); - salt = DatatypeConverter.parseHexBinary(fields[9]); - - hashStart = (long) start * blockSize; - img.seek(hashStart); - - int treeSize = calculateHashTreeSize(); - - hashTree = new byte[treeSize]; - img.readFully(hashTree); - } - - /** - * Reads verity data from the image file - */ - private void readVerityData() throws Exception { - try (RandomAccessFile img = new RandomAccessFile(image, "r")) { - imageSize = getMetadataPosition(img); - img.seek(imageSize); - - int magic = fromle(img.readInt()); - - if (magic != VERITY_MAGIC) { - throw new IllegalArgumentException("Invalid image: verity metadata not found"); - } - - int version = fromle(img.readInt()); - - if (version != VERITY_VERSION) { - throw new IllegalArgumentException("Invalid image: unknown metadata version"); - } - - signature = new byte[VERITY_SIGNATURE_SIZE]; - img.readFully(signature); - - int tableSize = fromle(img.readInt()); - - table = new byte[tableSize]; - img.readFully(table); - - readHashTree(img, table); - } - } - - /** - * Reads and validates verity metadata, and checks the signature against the - * given public key - * @param key Public key to use for signature verification - */ - public boolean verifyMetaData(PublicKey key) - throws Exception { - return Utils.verify(key, table, signature, - Utils.getSignatureAlgorithmIdentifier(key)); - } - - /** - * Hashes a block of data using a salt and checks of the results are expected - * @param hash The expected hash value - * @param data The data block to check - */ - private boolean checkBlock(byte[] hash, byte[] data) { - digest.reset(); - digest.update(salt); - digest.update(data); - return Arrays.equals(hash, digest.digest()); - } - - /** - * Verifies the root hash and the first N-1 levels of the hash tree - */ - private boolean verifyHashTree() throws Exception { - int hashOffset = 0; - int dataOffset = hashBlockSize; - - if (!checkBlock(rootHash, Arrays.copyOfRange(hashTree, 0, hashBlockSize))) { - System.err.println("Root hash mismatch"); - return false; - } - - for (int level = 0; level < hashBlocksLevel.size() - 1; level++) { - int blocks = hashBlocksLevel.get(level); - - for (int i = 0; i < blocks; i++) { - byte[] hashBlock = Arrays.copyOfRange(hashTree, - hashOffset + i * hashSize, - hashOffset + i * hashSize + hashSize); - - byte[] dataBlock = Arrays.copyOfRange(hashTree, - dataOffset + i * hashBlockSize, - dataOffset + i * hashBlockSize + hashBlockSize); - - if (!checkBlock(hashBlock, dataBlock)) { - System.err.printf("Hash mismatch at tree level %d, block %d\n", level, i); - return false; - } - } - - hashOffset = dataOffset; - hashOffsetForData = dataOffset; - dataOffset += blocks * hashBlockSize; - } - - return true; - } - - /** - * Validates the image against the hash tree - */ - public boolean verifyData() throws Exception { - if (!verifyHashTree()) { - return false; - } - - try (RandomAccessFile img = new RandomAccessFile(image, "r")) { - byte[] dataBlock = new byte[blockSize]; - int hashOffset = hashOffsetForData; - - for (int i = 0; (long) i * blockSize < imageSize; i++) { - byte[] hashBlock = Arrays.copyOfRange(hashTree, - hashOffset + i * hashSize, - hashOffset + i * hashSize + hashSize); - - img.readFully(dataBlock); - - if (!checkBlock(hashBlock, dataBlock)) { - System.err.printf("Hash mismatch at block %d\n", i); - return false; - } - } - } - - return true; - } - - /** - * Verifies the integrity of the image and the verity metadata - * @param key Public key to use for signature verification - */ - public boolean verify(PublicKey key) throws Exception { - return (verifyMetaData(key) && verifyData()); - } - - public static void main(String[] args) throws Exception { - Security.addProvider(new BouncyCastleProvider()); - PublicKey key = null; - - if (args.length == 3 && "-mincrypt".equals(args[1])) { - key = getMincryptPublicKey(args[2]); - } else if (args.length == 2) { - X509Certificate cert = Utils.loadPEMCertificate(args[1]); - key = cert.getPublicKey(); - } else { - System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem> | -mincrypt <mincrypt_key>"); - System.exit(1); - } - - VerityVerifier verifier = new VerityVerifier(args[0]); - - try { - if (verifier.verify(key)) { - System.err.println("Signature is VALID"); - System.exit(0); - } - } catch (Exception e) { - e.printStackTrace(System.err); - } - - System.exit(1); - } -} |