blob: b9e390e7c9b11251c87184ae02b95d9b98b0f78d [file] [log] [blame]
/*
* 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));
}