Home | History | Annotate | Download | only in verity
      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