Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2017 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.apksig.internal.util;
     18 
     19 import static java.nio.charset.StandardCharsets.UTF_8;
     20 import static junit.framework.TestCase.assertEquals;
     21 import static junit.framework.TestCase.assertNotNull;
     22 import static junit.framework.TestCase.fail;
     23 
     24 import com.android.apksig.util.DataSource;
     25 import com.android.apksig.util.DataSources;
     26 
     27 import org.junit.Test;
     28 import org.junit.runner.RunWith;
     29 import org.junit.runners.JUnit4;
     30 
     31 import java.io.IOException;
     32 import java.nio.ByteBuffer;
     33 import java.security.NoSuchAlgorithmException;
     34 
     35 
     36 /** Unit tests for {@link VerityTreeBuilder}. */
     37 @RunWith(JUnit4.class)
     38 public final class VerityTreeBuilderTest {
     39 
     40     @Test public void SHA256RootHashMatch() throws Exception {
     41         expectRootHash("random-data-4096-bytes", null,
     42                 "a3b013ea0f5d5ffbda26d5e84882faa4c051d592c04b8779bd1f0f4e95cc2657");
     43         expectRootHash("random-data-4096-bytes", new byte[] { },
     44                 "a3b013ea0f5d5ffbda26d5e84882faa4c051d592c04b8779bd1f0f4e95cc2657");
     45         expectRootHash("random-data-4096-bytes", new byte[] { 0x00 },
     46                 "cab1bcac3cf9b91151730c0de1880112d2c9865543d3fa56b534273c06667973");
     47         expectRootHash("random-data-8192-bytes", new byte[] { 0x10 },
     48                 "477afbaa5e884454bb95a8d63366362c21c0a5d7b8e5476b004692bf9a692a00");
     49 
     50         // 524289 requires additional tree level.
     51         expectRootHash("random-data-524287-bytes", new byte[] { 0x20 },
     52                 "39534c3a0bd27efcafb408e630248082156c80ab41789814749b33e325a93c62");
     53         expectRootHash("random-data-524288-bytes", new byte[] { 0x21 },
     54                 "34f8b9c33cd49b753b9341b2d8a4b83e59c5ae458ec6a85fbfebd49314c24d4e");
     55         expectRootHash("random-data-524289-bytes", new byte[] { 0x22 },
     56                 "1934793602f5e0b8c7aa7ed7e7acb42dca579ed11d8ac5ff9bb6d346f4222bd5");
     57         expectRootHash("random-data-525000-bytes", new byte[] { 0x23 },
     58                 "f63b718c01f569386d7de2e813d7b1e452322c638fb240af3ef01c2e6d317ee8");
     59     }
     60 
     61     private static void expectRootHash(String inputResource, byte[] salt, String expectedRootHash)
     62             throws IOException {
     63         assertEquals(expectedRootHash, generateRootHash(inputResource, salt));
     64     }
     65 
     66     private static String generateRootHash(String inputResource, byte[] salt) throws IOException {
     67         byte[] input = Resources.toByteArray(VerityTreeBuilderTest.class, inputResource);
     68         assertNotNull(input);
     69         try {
     70             VerityTreeBuilder builder = new VerityTreeBuilder(salt);
     71             return HexEncoding.encode(builder.generateVerityTreeRootHash(
     72                     DataSources.asDataSource(ByteBuffer.wrap(input))));
     73         } catch (NoSuchAlgorithmException e) {
     74             fail(e.getMessage());
     75             return null;
     76         }
     77     }
     78 
     79     private DataSource makeStringDataSource(String data) {
     80         return DataSources.asDataSource(ByteBuffer.wrap(data.getBytes(UTF_8)));
     81     }
     82 
     83     @Test public void generateVerityTreeRootHashFromDummyDataSource() throws Exception {
     84         // This sample was taken from src/test/resources/com/android/apksig/original.apk.
     85         byte[] sampleEoCDFromDisk = new byte[] {
     86             0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x79, 0x01,
     87             0x00, 0x00, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00
     88         };
     89         String expectedRootHash =
     90                 "7694e72c242107a5b4ce6091faf867e2f13c033b6b64faddcb13b3d698a8495a";
     91         {
     92             VerityTreeBuilder builder = new VerityTreeBuilder(null);
     93             byte[] rootHash = builder.generateVerityTreeRootHash(
     94                     DataSources.asDataSource(ByteBuffer.allocate(4096)),  // before Signing Block
     95                     makeStringDataSource("this is central directory (fake data)"),
     96                     DataSources.asDataSource(ByteBuffer.wrap(sampleEoCDFromDisk)));
     97             assertEquals(expectedRootHash, HexEncoding.encode(rootHash));
     98         }
     99 
    100         // File ending with different length of zeros get the same hash.  This is to match the
    101         // behavior of fs-verity, where it expects the client to add salt for the same level of
    102         // protection.
    103         {
    104             ByteBuffer fileTailWithDifferentLengthOfZeros = ByteBuffer.allocate(
    105                     sampleEoCDFromDisk.length + 1);
    106             fileTailWithDifferentLengthOfZeros.put(sampleEoCDFromDisk);
    107             fileTailWithDifferentLengthOfZeros.rewind();
    108 
    109             VerityTreeBuilder builder = new VerityTreeBuilder(null);
    110             byte[] rootHash = builder.generateVerityTreeRootHash(
    111                     DataSources.asDataSource(ByteBuffer.allocate(4096)),  // before Signing Block
    112                     makeStringDataSource("this is central directory (fake data)"),
    113                     DataSources.asDataSource(fileTailWithDifferentLengthOfZeros));
    114             assertEquals(expectedRootHash, HexEncoding.encode(rootHash));
    115         }
    116     }
    117 }
    118