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