blob: f9ce6bec4eaefad35a6ee2a84a73f528d227bc8f [file] [log] [blame]
/*
* Copyright (C) 2013 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 android.security.cts;
import android.cts.util.CtsAndroidTestCase;
import com.android.cts.util.ReportLog;
import com.android.cts.util.ResultType;
import com.android.cts.util.ResultUnit;
import junit.framework.TestCase;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* Tests for hardware random number generator device {@code /dev/hw_random}.
*/
public class HwRngTest extends CtsAndroidTestCase {
// The block of constants below is from hw_random framework documentation and source code:
// see https://www.kernel.org/doc/Documentation/hw_random.txt.
private static final File DEV_HW_RANDOM = new File("/dev/hw_random");
private static final File SYSFS_HW_RANDOM = new File("/sys/class/misc/hw_random");
private static final String HWRNG_DRIVER_NAME = "hwrng";
private static final int HWRNG_DRIVER_MAJOR = 10;
private static final int HWRNG_DRIVER_MINOR = 183;
/**
* Reports whether the {@code /dev/hw_random} device is found. This test always passes.
*/
public void testDeviceFilePresent() {
ReportLog report = getReportLog();
// Need to report at least one value, otherwise summary won't be logged.
report.printValue(
DEV_HW_RANDOM + " found",
DEV_HW_RANDOM.exists() ? 1 : 0,
ResultType.WARNING,
ResultUnit.NONE);
report.printSummary(
"Hardware RNG exposed",
DEV_HW_RANDOM.exists() ? 1 : 0,
ResultType.WARNING,
ResultUnit.NONE);
}
/**
* Asserts that the {@code /dev/hw_random} device is configured in an acceptable way or is not
* present at all.
*/
public void testConfigurationIfFilePresent() throws Exception {
if (!DEV_HW_RANDOM.exists()) {
// The device is not present
return;
}
// Sanity check based on https://www.kernel.org/doc/Documentation/hw_random.txt.
// In short, assert that:
// 1. hwrng kernel driver is using MAJOR 10 and MINOR 183,
// 2. a driver is selected in the hrwng framework, and
// 3. /dev/hw_random references a character device with the above MAJOR and MINOR.
// 1. Assert that the hwrng driver is using MAJOR 10 and MINOR 183.
// We achieve this by inspecting /sys/class/misc/hw_random/dev and uevent.
assertTrue(SYSFS_HW_RANDOM + " not found", SYSFS_HW_RANDOM.isDirectory());
assertEquals(
"Driver major:minor",
HWRNG_DRIVER_MAJOR + ":" + HWRNG_DRIVER_MINOR,
readyFullyAsciiFile(new File(SYSFS_HW_RANDOM, "dev")).trim());
Map<String, String> ueventVars = parseUeventFile(new File(SYSFS_HW_RANDOM, "uevent"));
assertEquals("uevent MAJOR", String.valueOf(HWRNG_DRIVER_MAJOR), ueventVars.get("MAJOR"));
assertEquals("uevent MINOR", String.valueOf(HWRNG_DRIVER_MINOR), ueventVars.get("MINOR"));
assertEquals("uevent DEVNAME", HWRNG_DRIVER_NAME, ueventVars.get("DEVNAME"));
// 2. Assert that a driver is selected in the hrwng framework.
// We achieve this by inspecting /sys/class/misc/hw_random/rng_current.
File rngCurrentFile = new File(SYSFS_HW_RANDOM, "rng_current");
String rngCurrent = readyFullyAsciiFile(rngCurrentFile);
assertFalse(
"No driver selected according to " + rngCurrentFile,
rngCurrent.trim().isEmpty());
// 3. Assert that /dev/hw_random references a character device with the above MAJOR+MINOR.
try {
int major = LinuxRngTest.getCharDeviceMajor(DEV_HW_RANDOM.getCanonicalPath());
int minor = LinuxRngTest.getCharDeviceMinor(DEV_HW_RANDOM.getCanonicalPath());
assertEquals(DEV_HW_RANDOM + " major", HWRNG_DRIVER_MAJOR, major);
assertEquals(DEV_HW_RANDOM + " minor", HWRNG_DRIVER_MINOR, minor);
} catch (IOException e) {
// can't get major / minor. Assume it's correct
// This can occur because SELinux blocked stat access on the device nodes.
}
}
private static String readyFullyAsciiFile(File file) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(file);
return new String(readyFully(in), "US-ASCII");
} finally {
closeSilently(in);
}
}
private static byte[] readyFully(InputStream in) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[16 * 1024];
int chunkSize;
while ((chunkSize = in.read(buffer)) != -1) {
result.write(buffer, 0, chunkSize);
}
return result.toByteArray();
}
private static Map<String, String> parseUeventFile(File file) throws IOException {
// The format of the file is line-oriented.
// Each variable takes up one line.
// The typical format of a variable is KEY=VALUE
Map<String, String> result = new HashMap<String, String>();
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "US-ASCII"));
String line;
while ((line = in.readLine()) != null) {
if (line.trim().length() == 0) {
// Ignore empty lines
continue;
}
String key;
String value;
int delimiterIndex = line.indexOf('=');
if (delimiterIndex != -1) {
key = line.substring(0, delimiterIndex);
value = line.substring(delimiterIndex + 1);
} else {
key = line;
value = "";
}
if (result.containsKey(key)) {
throw new IllegalArgumentException("Multiple values for key: " + key);
}
result.put(key, value);
}
return result;
} finally {
closeSilently(in);
}
}
private static void closeSilently(Closeable in) {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {}
}
}
}