CTS test for device encryption
Verify that CPU specific optimizations are enabled in kernel and
require encryption unless performance is below threshold.
Bug: 22297300
Change-Id: Ib7f8774e57e046b61b15e5c36fcfb01548e2c26a
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index 6bef886..2d55fb6 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -30,10 +30,14 @@
android_security_cts_NativeCodeTest.cpp \
android_security_cts_SELinuxTest.cpp \
android_security_cts_MMapExecutableTest.cpp \
- android_security_cts_AudioPolicyBinderTest.cpp
+ android_security_cts_AudioPolicyBinderTest.cpp \
+ android_security_cts_EncryptionTest.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SHARED_LIBRARIES := libnativehelper liblog libbinder libutils libmedia libselinux libdl
+LOCAL_SHARED_LIBRARIES := libnativehelper liblog libbinder libutils libmedia libselinux libdl libcutils libcrypto
+
+LOCAL_C_INCLUDES += ndk/sources/cpufeatures
+LOCAL_STATIC_LIBRARIES := cpufeatures
include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 1051a82..c60b866 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -25,6 +25,7 @@
extern int register_android_security_cts_SELinuxTest(JNIEnv*);
extern int register_android_security_cts_MMapExecutableTest(JNIEnv* env);
extern int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env);
+extern int register_android_security_cts_EncryptionTest(JNIEnv* env);
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
@@ -65,5 +66,9 @@
return JNI_ERR;
}
+ if (register_android_security_cts_EncryptionTest(env)) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_4;
}
diff --git a/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp b/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp
new file mode 100644
index 0000000..b9e390e
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <cpu-features.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <openssl/aes.h>
+#include <openssl/cpu.h>
+#include <openssl/evp.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <new>
+
+#define TEST_EVP_CIPHER EVP_aes_256_cbc()
+#define TEST_BUFSIZE (1 * 1024 * 1024) /* 1 MiB */
+#define TEST_ITERATIONS 100 /* MiB */
+#define TEST_THRESHOLD 2000 /* ms */
+
+/*
+ * Function: deviceIsEncrypted
+ * Purpose: Check the device is encrypted
+ * Parameters: none
+ * Returns: boolean: (true) if encrypted, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_deviceIsEncrypted(JNIEnv *, jobject)
+{
+ char prop_value[PROP_VALUE_MAX];
+ property_get("ro.crypto.state", prop_value, "");
+
+ jboolean rc = !strcmp(prop_value, "encrypted");
+ ALOGE("EncryptionTest::deviceIsEncrypted: %d", rc);
+
+ return rc;
+}
+
+/*
+ * Function: cpuHasAes
+ * Purpose: Check if we have an ARM CPU with AES instruction
+ * Parameters: none
+ * Returns: boolean: (true) if AES is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_cpuHasAes(JNIEnv *, jobject)
+{
+ jboolean rc = false;
+ AndroidCpuFamily family = android_getCpuFamily();
+ uint64_t features = android_getCpuFeatures();
+
+ if (family == ANDROID_CPU_FAMILY_ARM) {
+ rc = (features & ANDROID_CPU_ARM_FEATURE_AES) != 0;
+ } else if (family == ANDROID_CPU_FAMILY_ARM64) {
+ rc = (features & ANDROID_CPU_ARM64_FEATURE_AES) != 0;
+ }
+
+ ALOGE("EncryptionTest::cpuHasAes: %d", rc);
+ return rc;
+}
+
+/*
+ * Function: cpuHasNeon
+ * Purpose: Check if we have an ARM CPU with NEON instructions
+ * Parameters: none
+ * Returns: boolean: (true) if NEON is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_cpuHasNeon(JNIEnv *, jobject)
+{
+ jboolean rc = false;
+ AndroidCpuFamily family = android_getCpuFamily();
+
+ if (family == ANDROID_CPU_FAMILY_ARM) {
+ rc = (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
+ } else {
+ rc = (family == ANDROID_CPU_FAMILY_ARM64);
+ }
+
+ ALOGE("EncryptionTest::cpuHasNeon: %d", rc);
+ return rc;
+}
+
+/*
+ * Function: neonIsEnabled
+ * Purpose: Check if libcrypto is compiled with NEON support
+ * Parameters: none
+ * Returns: boolean: (true) if NEON is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_neonIsEnabled(JNIEnv *, jobject)
+{
+#if defined(OPENSSL_ARM) || defined(OPENSSL_AARCH64)
+ jboolean rc = CRYPTO_is_NEON_capable();
+#else
+ jboolean rc = false;
+#endif
+
+ ALOGE("EncryptionTest::neonIsEnabled: %d", rc);
+ return rc;
+}
+
+static inline uint64_t ns()
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+/*
+ * Function: aesIsFast
+ * Purpose: Test if AES performance is sufficient to require encryption
+ * Parameters: none
+ * Returns: boolean: (true) if AES performance is acceptable, (false) otherwise
+ * Exceptions: InvalidKeyException if EVP_DecryptInit fails, OutOfMemoryError
+ * if memory allocation fails.
+ */
+static jboolean android_security_cts_EncryptionTest_aesIsFast(JNIEnv *env, jobject)
+{
+ EVP_CIPHER_CTX ctx;
+ uint8_t *buf;
+ uint8_t key[EVP_CIPHER_key_length(TEST_EVP_CIPHER)];
+ uint8_t iv[EVP_CIPHER_iv_length(TEST_EVP_CIPHER)];
+
+ memset(key, 0x42, sizeof(key));
+ memset(iv, 0x11, sizeof(iv));
+
+ EVP_CIPHER_CTX_init(&ctx);
+
+ if (!EVP_DecryptInit(&ctx, TEST_EVP_CIPHER, key, iv)) {
+ jniThrowException(env, "java/security/InvalidKeyException",
+ "EVP_DecryptInit failed");
+ return false;
+ }
+
+ buf = new (std::nothrow) uint8_t[TEST_BUFSIZE +
+ EVP_CIPHER_block_size(TEST_EVP_CIPHER)];
+
+ if (!buf) {
+ jniThrowException(env, "java/lang/OutOfMemoryError",
+ "Failed to allocate test buffer");
+ return false;
+ }
+
+ memset(buf, 0xF0, TEST_BUFSIZE);
+
+ int len;
+ uint64_t t = ns();
+
+ for (int i = 0; i < TEST_ITERATIONS; ++i) {
+ EVP_DecryptUpdate(&ctx, buf, &len, buf, TEST_BUFSIZE);
+ }
+
+ t = ns() - t;
+
+ delete[] buf;
+
+ unsigned long ms = (unsigned long)(t / 1000000);
+ double speed =
+ (double)(TEST_ITERATIONS * TEST_BUFSIZE / (1024 * 1024)) * 1000.0 / ms;
+
+ ALOGE("EncryptionTest::aesIsFast: %u iterations in %lu ms (%.01lf MiB/s) "
+ "(threshold %u ms)", TEST_ITERATIONS, ms, speed, TEST_THRESHOLD);
+
+ return ms < TEST_THRESHOLD;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "deviceIsEncrypted", "()Z",
+ (void *) android_security_cts_EncryptionTest_deviceIsEncrypted },
+ { "cpuHasAes", "()Z",
+ (void *) android_security_cts_EncryptionTest_cpuHasAes },
+ { "cpuHasNeon", "()Z",
+ (void *) android_security_cts_EncryptionTest_cpuHasNeon },
+ { "neonIsEnabled", "()Z",
+ (void *) android_security_cts_EncryptionTest_neonIsEnabled },
+ { "aesIsFast", "()Z",
+ (void *) android_security_cts_EncryptionTest_aesIsFast }
+};
+
+int register_android_security_cts_EncryptionTest(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/security/cts/EncryptionTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
new file mode 100644
index 0000000..bd9a458
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.test.AndroidTestCase;
+import junit.framework.TestCase;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.util.Log;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EncryptionTest extends AndroidTestCase {
+
+ static {
+ System.loadLibrary("ctssecurity_jni");
+ }
+
+ private static final String TAG = "EncryptionTest";
+
+ private static final String crypto = "/proc/crypto";
+
+ private static native boolean deviceIsEncrypted();
+
+ private static native boolean cpuHasAes();
+
+ private static native boolean cpuHasNeon();
+
+ private static native boolean neonIsEnabled();
+
+ private static native boolean aesIsFast();
+
+ private boolean hasKernelCrypto(String driver) throws Exception {
+ BufferedReader br = new BufferedReader(new FileReader(crypto));
+ Pattern p = Pattern.compile("driver\\s*:\\s*" + driver);
+
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (p.matcher(line).matches()) {
+ Log.i(TAG, crypto + " has " + driver + " (" + line + ")");
+ return true;
+ }
+ }
+ } finally {
+ br.close();
+ }
+
+ return false;
+ }
+
+ private boolean hasLowRAM() {
+ ActivityManager activityManager =
+ (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
+
+ return activityManager.isLowRamDevice();
+ }
+
+ public void testConfig() throws Exception {
+ if (cpuHasAes()) {
+ // If CPU has AES CE, it must be enabled in kernel
+ assertTrue(crypto + " is missing xts-aes-ce",
+ hasKernelCrypto("xts-aes-ce"));
+ } else if (cpuHasNeon()) {
+ // Otherwise, if CPU has NEON, it must be enabled
+ assertTrue(crypto + " is missing xts-aes-neon (or xts-aes-neonbs)",
+ hasKernelCrypto("xts-aes-neon") ||
+ hasKernelCrypto("xts-aes-neonbs") ||
+ hasKernelCrypto("aes-asm")); // Not recommended alone
+ }
+
+ if (cpuHasNeon()) {
+ assertTrue("libcrypto must have NEON", neonIsEnabled());
+ }
+ }
+
+ public void testEncryption() throws Exception {
+ if (deviceIsEncrypted()) {
+ return;
+ }
+
+ // Optional for low RAM devices
+ if (hasLowRAM()) {
+ Log.i(TAG, "hasLowRAM: true");
+ return;
+ }
+
+ // Required if performance is sufficient
+ assertFalse("Device encryption is required", aesIsFast());
+ }
+}