1 /* 2 * Copyright (C) 2010 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.server; 18 19 import static org.junit.Assert.assertArrayEquals; 20 21 import android.content.Context; 22 import android.test.AndroidTestCase; 23 24 import org.junit.Test; 25 26 import java.io.File; 27 import java.nio.file.Files; 28 import java.util.Arrays; 29 30 /** 31 * Tests for {@link com.android.server.EntropyMixer} 32 */ 33 public class EntropyMixerTest extends AndroidTestCase { 34 35 private static final int SEED_FILE_SIZE = EntropyMixer.SEED_FILE_SIZE; 36 37 private File dir; 38 private File seedFile; 39 private File randomReadDevice; 40 private File randomWriteDevice; 41 42 @Override setUp()43 public void setUp() throws Exception { 44 dir = getContext().getDir("test", Context.MODE_PRIVATE); 45 seedFile = createTempFile(dir, "entropy.dat"); 46 randomReadDevice = createTempFile(dir, "urandomRead"); 47 randomWriteDevice = createTempFile(dir, "urandomWrite"); 48 } 49 createTempFile(File dir, String prefix)50 private File createTempFile(File dir, String prefix) throws Exception { 51 File file = File.createTempFile(prefix, null, dir); 52 file.deleteOnExit(); 53 return file; 54 } 55 repeatByte(byte b, int length)56 private byte[] repeatByte(byte b, int length) { 57 byte[] data = new byte[length]; 58 Arrays.fill(data, b); 59 return data; 60 } 61 62 // Test initializing the EntropyMixer when the seed file doesn't exist yet. 63 @Test testInitFirstBoot()64 public void testInitFirstBoot() throws Exception { 65 seedFile.delete(); 66 67 byte[] urandomInjectedData = repeatByte((byte) 0x01, SEED_FILE_SIZE); 68 Files.write(randomReadDevice.toPath(), urandomInjectedData); 69 70 // The constructor should have the side effect of writing to 71 // randomWriteDevice and creating seedFile. 72 new EntropyMixer(getContext(), seedFile, randomReadDevice, randomWriteDevice); 73 74 // Since there was no old seed file, the data that was written to 75 // randomWriteDevice should contain only device-specific information. 76 assertTrue(isDeviceSpecificInfo(Files.readAllBytes(randomWriteDevice.toPath()))); 77 78 // The seed file should have been created. 79 validateSeedFile(seedFile, new byte[0], urandomInjectedData); 80 } 81 82 // Test initializing the EntropyMixer when the seed file already exists. 83 @Test testInitNonFirstBoot()84 public void testInitNonFirstBoot() throws Exception { 85 byte[] previousSeed = repeatByte((byte) 0x01, SEED_FILE_SIZE); 86 Files.write(seedFile.toPath(), previousSeed); 87 88 byte[] urandomInjectedData = repeatByte((byte) 0x02, SEED_FILE_SIZE); 89 Files.write(randomReadDevice.toPath(), urandomInjectedData); 90 91 // The constructor should have the side effect of writing to 92 // randomWriteDevice and updating seedFile. 93 new EntropyMixer(getContext(), seedFile, randomReadDevice, randomWriteDevice); 94 95 // The data that was written to randomWriteDevice should consist of the 96 // previous seed followed by the device-specific information. 97 byte[] dataWrittenToUrandom = Files.readAllBytes(randomWriteDevice.toPath()); 98 byte[] firstPartWritten = Arrays.copyOf(dataWrittenToUrandom, SEED_FILE_SIZE); 99 byte[] secondPartWritten = 100 Arrays.copyOfRange( 101 dataWrittenToUrandom, SEED_FILE_SIZE, dataWrittenToUrandom.length); 102 assertArrayEquals(previousSeed, firstPartWritten); 103 assertTrue(isDeviceSpecificInfo(secondPartWritten)); 104 105 // The seed file should have been updated. 106 validateSeedFile(seedFile, previousSeed, urandomInjectedData); 107 } 108 isDeviceSpecificInfo(byte[] data)109 private boolean isDeviceSpecificInfo(byte[] data) { 110 return new String(data).startsWith(EntropyMixer.DEVICE_SPECIFIC_INFO_HEADER); 111 } 112 validateSeedFile(File seedFile, byte[] previousSeed, byte[] urandomInjectedData)113 private void validateSeedFile(File seedFile, byte[] previousSeed, byte[] urandomInjectedData) 114 throws Exception { 115 final int unhashedLen = SEED_FILE_SIZE - 32; 116 byte[] newSeed = Files.readAllBytes(seedFile.toPath()); 117 assertEquals(SEED_FILE_SIZE, newSeed.length); 118 assertEquals(SEED_FILE_SIZE, urandomInjectedData.length); 119 assertFalse(Arrays.equals(newSeed, previousSeed)); 120 // The new seed should consist of the first SEED_FILE_SIZE - 32 bytes 121 // that were read from urandom, followed by a 32-byte hash that should 122 // *not* be the same as the last 32 bytes that were read from urandom. 123 byte[] firstPart = Arrays.copyOf(newSeed, unhashedLen); 124 byte[] secondPart = Arrays.copyOfRange(newSeed, unhashedLen, SEED_FILE_SIZE); 125 byte[] firstPartInjected = Arrays.copyOf(urandomInjectedData, unhashedLen); 126 byte[] secondPartInjected = 127 Arrays.copyOfRange(urandomInjectedData, unhashedLen, SEED_FILE_SIZE); 128 assertArrayEquals(firstPart, firstPartInjected); 129 assertFalse(Arrays.equals(secondPart, secondPartInjected)); 130 } 131 } 132