Adding tests to verify `EVP_PKEY_from_keystore2` API [Keystore2-engine].
1. Generate RSA key and grant it to a user. In user context load the
key using `EVP_PKEY_from_keystore` and perform sign and verify
opeearions.
[keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success]
2. Generate EC key and grant it to a user. In user context load the
key using `EVP_PKEY_from_keystore` and perform sign and verify
operations.
[keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success]
3. Generate RSA key and grant it to a user. Re-encode the certificate
as PEM and update the certificate using `updateSubcomponents`.
In user context load the key using `EVP_PKEY_from_keystore` and
perform sign and verify operations.
Bug: 201343811
Test: atest keystore2_client_tests
Change-Id: I7dafd598f4198e11103cd11695b2f67636f24755
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
index cb75cde..52a2aef 100644
--- a/keystore-engine/Android.bp
+++ b/keystore-engine/Android.bp
@@ -36,7 +36,7 @@
],
shared_libs: [
- "android.system.keystore2-V1-ndk",
+ "android.system.keystore2-V3-ndk",
"libbinder_ndk",
"libcrypto",
"libcutils",
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
index e4c4968..3422ce8 100644
--- a/keystore2/test_utils/key_generations.rs
+++ b/keystore2/test_utils/key_generations.rs
@@ -299,6 +299,9 @@
/// Error code to indicate error in ASN.1 DER-encoded data creation.
#[error("Failed to create and encode ASN.1 data.")]
DerEncodeFailed,
+ /// Error code to indicate error while using keystore-engine API.
+ #[error("Failed to perform crypto op using keystore-engine APIs.")]
+ Keystore2EngineOpFailed,
}
/// Keystore2 error mapping.
diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp
index 78dd2d7..0c8b0c8 100644
--- a/keystore2/tests/Android.bp
+++ b/keystore2/tests/Android.bp
@@ -57,6 +57,7 @@
"libkeymaster_portable",
"libkeymaster_messages",
"libcppbor_external",
+ "libkeystore-engine",
],
require_root: true,
}
@@ -80,6 +81,7 @@
"libkeymaster_portable",
"libkeymaster_messages",
"libcppbor_external",
+ "libkeystore-engine",
],
}
diff --git a/keystore2/tests/ffi_test_utils.cpp b/keystore2/tests/ffi_test_utils.cpp
index de20d83..47dd7a4 100644
--- a/keystore2/tests/ffi_test_utils.cpp
+++ b/keystore2/tests/ffi_test_utils.cpp
@@ -2,11 +2,12 @@
#include <iostream>
+#include <android-base/logging.h>
+
#include <KeyMintAidlTestBase.h>
#include <aidl/android/hardware/security/keymint/ErrorCode.h>
#include <keymaster/UniquePtr.h>
-#include <memory>
#include <vector>
#include <hardware/keymaster_defs.h>
@@ -16,7 +17,6 @@
#include <keymaster/km_openssl/attestation_record.h>
#include <keymaster/km_openssl/openssl_err.h>
#include <keymaster/km_openssl/openssl_utils.h>
-#include <openssl/asn1t.h>
using aidl::android::hardware::security::keymint::ErrorCode;
@@ -24,6 +24,9 @@
#define LENGTH_MASK 0x80
#define LENGTH_VALUE_MASK 0x7F
+/* EVP_PKEY_from_keystore is from system/security/keystore-engine. */
+extern "C" EVP_PKEY* EVP_PKEY_from_keystore(const char* key_id);
+
/**
* ASN.1 structure for `KeyDescription` Schema.
* See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema.
@@ -84,6 +87,8 @@
void operator()(TEST_SECURE_KEY_WRAPPER* p) { TEST_SECURE_KEY_WRAPPER_free(p); }
};
+const std::string keystore2_grant_id_prefix("ks2_keystore-engine_grant_id:");
+
/* This function extracts a certificate from the certs_chain_buffer at the given
* offset. Each DER encoded certificate starts with TAG_SEQUENCE followed by the
* total length of the certificate. The length of the certificate is determined
@@ -364,3 +369,137 @@
return cxx_result;
}
+
+/**
+ * Perform EC/RSA sign operation using `EVP_PKEY`.
+ */
+bool performSignData(const char* data, size_t data_len, EVP_PKEY* pkey, unsigned char** signature,
+ size_t* signature_len) {
+ // Create the signing context
+ EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
+ if (md_ctx == NULL) {
+ LOG(ERROR) << "Failed to create signing context";
+ return false;
+ }
+
+ // Initialize the signing operation
+ if (EVP_DigestSignInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+ LOG(ERROR) << "Failed to initialize signing operation";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Sign the data
+ if (EVP_DigestSignUpdate(md_ctx, data, data_len) != 1) {
+ LOG(ERROR) << "Failed to sign data";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Determine the length of the signature
+ if (EVP_DigestSignFinal(md_ctx, NULL, signature_len) != 1) {
+ LOG(ERROR) << "Failed to determine signature length";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Allocate memory for the signature
+ *signature = (unsigned char*)malloc(*signature_len);
+ if (*signature == NULL) {
+ LOG(ERROR) << "Failed to allocate memory for the signature";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Perform the final signing operation
+ if (EVP_DigestSignFinal(md_ctx, *signature, signature_len) != 1) {
+ LOG(ERROR) << "Failed to perform signing operation";
+ free(*signature);
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ EVP_MD_CTX_free(md_ctx);
+ return true;
+}
+
+/**
+ * Perform EC/RSA verify operation using `EVP_PKEY`.
+ */
+int performVerifySignature(const char* data, size_t data_len, EVP_PKEY* pkey,
+ const unsigned char* signature, size_t signature_len) {
+ // Create the verification context
+ EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
+ if (md_ctx == NULL) {
+ LOG(ERROR) << "Failed to create verification context";
+ return false;
+ }
+
+ // Initialize the verification operation
+ if (EVP_DigestVerifyInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+ LOG(ERROR) << "Failed to initialize verification operation";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Verify the data
+ if (EVP_DigestVerifyUpdate(md_ctx, data, data_len) != 1) {
+ LOG(ERROR) << "Failed to verify data";
+ EVP_MD_CTX_free(md_ctx);
+ return false;
+ }
+
+ // Perform the verification operation
+ int ret = EVP_DigestVerifyFinal(md_ctx, signature, signature_len);
+ EVP_MD_CTX_free(md_ctx);
+
+ return ret == 1;
+}
+
+/**
+ * Extract the `EVP_PKEY` for the given KeyMint Key and perform Sign/Verify operations
+ * using extracted `EVP_PKEY`.
+ */
+bool performCryptoOpUsingKeystoreEngine(int64_t grant_id) {
+ const int KEY_ID_LEN = 20;
+ char key_id[KEY_ID_LEN] = "";
+ snprintf(key_id, KEY_ID_LEN, "%" PRIx64, grant_id);
+ std::string str_key = std::string(keystore2_grant_id_prefix) + key_id;
+ bool result = false;
+
+#if defined(OPENSSL_IS_BORINGSSL)
+ EVP_PKEY* evp = EVP_PKEY_from_keystore(str_key.c_str());
+ if (!evp) {
+ LOG(ERROR) << "Error while loading a key from keystore-engine";
+ return false;
+ }
+
+ int algo_type = EVP_PKEY_id(evp);
+ if (algo_type != EVP_PKEY_RSA && algo_type != EVP_PKEY_EC) {
+ LOG(ERROR) << "Unsupported Algorithm. Only RSA and EC are allowed.";
+ EVP_PKEY_free(evp);
+ return false;
+ }
+
+ unsigned char* signature = NULL;
+ size_t signature_len = 0;
+ const char* INPUT_DATA = "MY MESSAGE FOR SIGN";
+ size_t data_len = strlen(INPUT_DATA);
+ if (!performSignData(INPUT_DATA, data_len, evp, &signature, &signature_len)) {
+ LOG(ERROR) << "Failed to sign data";
+ EVP_PKEY_free(evp);
+ return false;
+ }
+
+ result = performVerifySignature(INPUT_DATA, data_len, evp, signature, signature_len);
+ if (!result) {
+ LOG(ERROR) << "Signature verification failed";
+ } else {
+ LOG(INFO) << "Signature verification success";
+ }
+
+ free(signature);
+ EVP_PKEY_free(evp);
+#endif
+ return result;
+}
diff --git a/keystore2/tests/ffi_test_utils.hpp b/keystore2/tests/ffi_test_utils.hpp
index b8c7c48..32f29f4 100644
--- a/keystore2/tests/ffi_test_utils.hpp
+++ b/keystore2/tests/ffi_test_utils.hpp
@@ -9,3 +9,4 @@
rust::Vec<rust::u8> iv,
rust::Vec<rust::u8> tag);
CxxResult buildAsn1DerEncodedWrappedKeyDescription();
+bool performCryptoOpUsingKeystoreEngine(int64_t grant_id);
diff --git a/keystore2/tests/ffi_test_utils.rs b/keystore2/tests/ffi_test_utils.rs
index 066d4a1..689713a 100644
--- a/keystore2/tests/ffi_test_utils.rs
+++ b/keystore2/tests/ffi_test_utils.rs
@@ -31,6 +31,7 @@
tag: Vec<u8>,
) -> CxxResult;
fn buildAsn1DerEncodedWrappedKeyDescription() -> CxxResult;
+ fn performCryptoOpUsingKeystoreEngine(grant_id: i64) -> bool;
}
}
@@ -78,3 +79,11 @@
pub fn create_wrapped_key_additional_auth_data() -> Result<Vec<u8>, Error> {
get_result(ffi::buildAsn1DerEncodedWrappedKeyDescription())
}
+
+pub fn perform_crypto_op_using_keystore_engine(grant_id: i64) -> Result<bool, Error> {
+ if ffi::performCryptoOpUsingKeystoreEngine(grant_id) {
+ return Ok(true);
+ }
+
+ Err(Error::Keystore2EngineOpFailed)
+}
diff --git a/keystore2/tests/keystore2_client_keystore_engine_tests.rs b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
new file mode 100644
index 0000000..1aed8e6
--- /dev/null
+++ b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
@@ -0,0 +1,299 @@
+// Copyright 2023, 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.
+
+use nix::unistd::{Gid, Uid};
+use rustutils::users::AID_USER_OFFSET;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose,
+ PaddingMode::PaddingMode, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+ IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
+};
+
+use keystore2_test_utils::{authorizations::AuthSetBuilder, get_keystore_service, run_as};
+
+use crate::ffi_test_utils::perform_crypto_op_using_keystore_engine;
+
+use openssl::x509::X509;
+
+fn generate_rsa_key_and_grant_to_user(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: &str,
+ grantee_uid: i32,
+ access_vector: i32,
+) -> binder::Result<KeyDescriptor> {
+ let gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .rsa_public_exponent(65537)
+ .key_size(2048)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .padding_mode(PaddingMode::NONE)
+ .digest(Digest::NONE);
+
+ let key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .expect("Failed to generate RSA Key.");
+
+ assert!(key_metadata.certificate.is_some());
+
+ keystore2.grant(&key_metadata.key, grantee_uid, access_vector)
+}
+
+fn generate_ec_key_and_grant_to_user(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: &str,
+ grantee_uid: i32,
+ access_vector: i32,
+) -> binder::Result<KeyDescriptor> {
+ let gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::NONE)
+ .ec_curve(EcCurve::P_256);
+
+ let key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .expect("Failed to generate EC Key.");
+
+ assert!(key_metadata.certificate.is_some());
+
+ keystore2.grant(&key_metadata.key, grantee_uid, access_vector)
+}
+
+fn generate_key_and_grant_to_user(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: &str,
+ grantee_uid: u32,
+ algo: Algorithm,
+) -> Result<i64, Box<dyn std::error::Error>> {
+ let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0;
+
+ assert!(matches!(algo, Algorithm::RSA | Algorithm::EC));
+
+ let grant_key = match algo {
+ Algorithm::RSA => generate_rsa_key_and_grant_to_user(
+ keystore2,
+ sec_level,
+ alias,
+ grantee_uid.try_into().unwrap(),
+ access_vector,
+ )
+ .unwrap(),
+ Algorithm::EC => generate_ec_key_and_grant_to_user(
+ keystore2,
+ sec_level,
+ alias,
+ grantee_uid.try_into().unwrap(),
+ access_vector,
+ )
+ .unwrap(),
+ _ => panic!("Unsupported algorithms"),
+ };
+
+ assert_eq!(grant_key.domain, Domain::GRANT);
+
+ Ok(grant_key.nspace)
+}
+
+fn perform_crypto_op_using_granted_key(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ grant_key_nspace: i64,
+) {
+ // Load the granted key from Keystore2-Engine API and perform crypto operations.
+ assert!(perform_crypto_op_using_keystore_engine(grant_key_nspace).unwrap());
+
+ // Delete the granted key.
+ keystore2
+ .deleteKey(&KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: grant_key_nspace,
+ alias: None,
+ blob: None,
+ })
+ .unwrap();
+}
+
+#[test]
+fn keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success() {
+ static TARGET_SU_CTX: &str = "u:r:su:s0";
+
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
+ let grant_key_nspace = unsafe {
+ run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "keystore2_engine_rsa_key";
+ generate_key_and_grant_to_user(
+ &keystore2,
+ &sec_level,
+ alias,
+ GRANTEE_UID,
+ Algorithm::RSA,
+ )
+ .unwrap()
+ })
+ };
+
+ // In grantee context load the key and try to perform crypto operation.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
+ },
+ )
+ };
+}
+
+#[test]
+fn keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success() {
+ static TARGET_SU_CTX: &str = "u:r:su:s0";
+
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
+ let grant_key_nspace = unsafe {
+ run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "keystore2_engine_ec_test_key";
+ generate_key_and_grant_to_user(
+ &keystore2,
+ &sec_level,
+ alias,
+ GRANTEE_UID,
+ Algorithm::EC,
+ )
+ .unwrap()
+ })
+ };
+
+ // In grantee context load the key and try to perform crypto operation.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
+ },
+ )
+ };
+}
+
+#[test]
+fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() {
+ static TARGET_SU_CTX: &str = "u:r:su:s0";
+
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ // Generate a key and re-encode it's certificate as PEM and update it and
+ // grant it to a user with GET_INFO|USE|DELETE key permissions.
+ let grant_key_nspace = unsafe {
+ run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "keystore2_engine_rsa_pem_pub_key";
+ let grant_key_nspace = generate_key_and_grant_to_user(
+ &keystore2,
+ &sec_level,
+ alias,
+ GRANTEE_UID,
+ Algorithm::RSA,
+ )
+ .unwrap();
+
+ // Update certificate with encodeed PEM data.
+ let key_entry_response = keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+ .unwrap();
+ let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap();
+ let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
+ let cert_pem = cert.to_pem().unwrap();
+ keystore2
+ .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None)
+ .expect("updateSubcomponent failed.");
+
+ grant_key_nspace
+ })
+ };
+
+ // In grantee context load the key and try to perform crypto operation.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
+ },
+ )
+ };
+}
diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs
index 07a298a..9be0bf8 100644
--- a/keystore2/tests/keystore2_client_tests.rs
+++ b/keystore2/tests/keystore2_client_tests.rs
@@ -23,6 +23,7 @@
pub mod keystore2_client_import_keys_tests;
pub mod keystore2_client_key_agreement_tests;
pub mod keystore2_client_key_id_domain_tests;
+pub mod keystore2_client_keystore_engine_tests;
pub mod keystore2_client_list_entries_tests;
pub mod keystore2_client_operation_tests;
pub mod keystore2_client_rsa_key_tests;