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());
+    }
+}