summaryrefslogtreecommitdiff
path: root/keystore2/tests/keystore2_client_ec_key_tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'keystore2/tests/keystore2_client_ec_key_tests.rs')
-rw-r--r--keystore2/tests/keystore2_client_ec_key_tests.rs517
1 files changed, 517 insertions, 0 deletions
diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs
new file mode 100644
index 00000000..c2034ded
--- /dev/null
+++ b/keystore2/tests/keystore2_client_ec_key_tests.rs
@@ -0,0 +1,517 @@
+// Copyright 2022, 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::{getuid, Gid, Uid};
+use rustutils::users::AID_USER_OFFSET;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+ ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
+};
+
+use crate::keystore2_client_test_utils::{
+ delete_app_key, execute_op_run_as_child, perform_sample_sign_operation, BarrierReached,
+ ForcedOp, TestOutcome,
+};
+
+macro_rules! test_ec_sign_key_op_success {
+ ( $test_name:ident, $digest:expr, $ec_curve:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_ec_sign_key_op_success(stringify!($test_name), $digest, $ec_curve);
+ }
+ };
+}
+
+macro_rules! test_ec_sign_key_op_with_none_or_md5_digest {
+ ( $test_name:ident, $digest:expr, $ec_curve:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_ec_sign_key_op_with_none_or_md5_digest(
+ stringify!($test_name),
+ $digest,
+ $ec_curve,
+ );
+ }
+ };
+}
+
+fn create_ec_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ digest: Digest,
+ ec_curve: EcCurve,
+) -> binder::Result<CreateOperationResponse> {
+ let key_metadata =
+ key_generations::generate_ec_key(sec_level, domain, nspace, alias, ec_curve, digest)?;
+
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ )
+}
+
+fn perform_ec_sign_key_op_success(alias: &str, digest: Digest, ec_curve: EcCurve) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let op_response = create_ec_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ digest,
+ ec_curve,
+ )
+ .unwrap();
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+fn perform_ec_sign_key_op_with_none_or_md5_digest(alias: &str, digest: Digest, ec_curve: EcCurve) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ match key_generations::map_ks_error(create_ec_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ digest,
+ ec_curve,
+ )) {
+ Ok(op_response) => {
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+ }
+ Err(e) => {
+ assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_DIGEST));
+ assert!(digest == Digest::NONE || digest == Digest::MD5);
+ }
+ }
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+// Below macros generate tests for generating EC keys with curves EcCurve::P_224, EcCurve::P_256,
+// EcCurve::P_384, EcCurve::P_521 and various digest modes. Tests tries to create operations using
+// the generated keys. Operations with digest modes `SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and
+// SHA-2 512` should be created successfully. Creation of operations with digest modes NONE and
+// MD5 should fail with an error code `UNSUPPORTED_DIGEST`.
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p224,
+ Digest::NONE,
+ EcCurve::P_224
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p224,
+ Digest::MD5,
+ EcCurve::P_224
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p224, Digest::SHA1, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p224, Digest::SHA_2_224, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p224, Digest::SHA_2_256, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p224, Digest::SHA_2_384, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p224, Digest::SHA_2_512, EcCurve::P_224);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p256,
+ Digest::NONE,
+ EcCurve::P_256
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p256,
+ Digest::MD5,
+ EcCurve::P_256
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p256, Digest::SHA1, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p256, Digest::SHA_2_224, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p256, Digest::SHA_2_256, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p256, Digest::SHA_2_384, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p256, Digest::SHA_2_512, EcCurve::P_256);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p384,
+ Digest::NONE,
+ EcCurve::P_384
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p384,
+ Digest::MD5,
+ EcCurve::P_384
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p384, Digest::SHA1, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p384, Digest::SHA_2_224, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p384, Digest::SHA_2_256, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p384, Digest::SHA_2_384, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p384, Digest::SHA_2_512, EcCurve::P_384);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p521,
+ Digest::NONE,
+ EcCurve::P_521
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p521,
+ Digest::MD5,
+ EcCurve::P_521
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p521, Digest::SHA1, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p521, Digest::SHA_2_224, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p521, Digest::SHA_2_256, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p521, Digest::SHA_2_384, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p521, Digest::SHA_2_512, EcCurve::P_521);
+
+/// This test will try to load the key with Domain::BLOB.
+/// INVALID_ARGUMENT error is expected.
+#[test]
+fn keystore2_get_key_entry_blob_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ // Generate a key with domain as BLOB.
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Try to load the key using above generated KeyDescriptor.
+ let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+
+ // Delete the generated key blob.
+ sec_level.deleteKey(&key_metadata.key).unwrap();
+}
+
+/// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected.
+#[test]
+fn keystore2_generate_key_invalid_domain() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_invalid_test_key_{}", getuid());
+
+ let result = key_generations::map_ks_error(key_generations::generate_ec_key(
+ &sec_level,
+ Domain(99), // Invalid domain.
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+}
+
+/// Try to generate a EC key without providing the curve.
+/// `UNSUPPORTED_EC_CURVE or UNSUPPORTED_KEY_SIZE` error response is expected.
+#[test]
+fn keystore2_generate_ec_key_missing_curve() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_ec_no_curve_test_key_{}", getuid());
+
+ // Don't provide EC curve.
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ let err = result.unwrap_err();
+ assert!(matches!(
+ err,
+ Error::Km(ErrorCode::UNSUPPORTED_EC_CURVE) | Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE)
+ ));
+}
+
+/// Try to generate a EC key with curve `CURVE_25519` having `SIGN and AGREE_KEY` purposes.
+/// `INCOMPATIBLE_PURPOSE` error response is expected.
+#[test]
+fn keystore2_generate_ec_key_25519_multi_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_ec_no_curve_test_key_{}", getuid());
+
+ // Specify `SIGN and AGREE_KEY` purposes.
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .ec_curve(EcCurve::CURVE_25519)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::AGREE_KEY)
+ .digest(Digest::SHA_2_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate EC key with curve `CURVE_25519` and digest mode NONE. Try to create an operation using
+/// generated key. `CURVE_25519` key should support `Digest::NONE` digest mode and test should be
+/// able to create an operation successfully.
+#[test]
+fn keystore2_ec_25519_generate_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_ec_25519_none_test_key_gen_{}", getuid());
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ EcCurve::CURVE_25519,
+ Digest::NONE,
+ )
+ .unwrap();
+
+ let op_response = sec_level
+ .createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::NONE),
+ false,
+ )
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+}
+
+/// Generate EC keys with curve `CURVE_25519` and digest modes `MD5, SHA1, SHA-2 224, SHA-2 256,
+/// SHA-2 384 and SHA-2 512`. Try to create operations using generated keys. `CURVE_25519` keys
+/// shouldn't support these digest modes. Test should fail to create operations with an error
+/// `UNSUPPORTED_DIGEST`.
+#[test]
+fn keystore2_ec_25519_generate_key_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let digests = [
+ Digest::MD5,
+ Digest::SHA1,
+ Digest::SHA_2_224,
+ Digest::SHA_2_256,
+ Digest::SHA_2_384,
+ Digest::SHA_2_512,
+ ];
+
+ for digest in digests {
+ let alias = format!("ks_ec_25519_test_key_gen_{}{}", getuid(), digest.0);
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::CURVE_25519,
+ digest,
+ )
+ .unwrap();
+
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+ }
+}
+
+/// Generate a EC key with `SHA_2_256` digest mode. Try to create an operation with digest mode
+/// other than `SHA_2_256`. Creation of an operation with generated key should fail with
+/// `INCOMPATIBLE_DIGEST` error as there is a mismatch of digest mode in key authorizations.
+#[test]
+fn keystore2_create_op_with_incompatible_key_digest() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_ec_test_incomp_key_digest";
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .unwrap();
+
+ let digests =
+ [Digest::NONE, Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_384, Digest::SHA_2_512];
+
+ for digest in digests {
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err());
+ }
+}
+
+/// Generate a key in client#1 and try to use it in other client#2.
+/// Client#2 should fail to load the key as the it doesn't own the client#1 generated key.
+#[test]
+fn keystore2_key_owner_validation() {
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID_1: u32 = 10601;
+
+ let uid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1;
+ let gid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1;
+ let alias = "ks_owner_check_test_key";
+
+ // Client#1: Generate a key and create an operation using generated key.
+ // Wait until the parent notifies to continue. Once the parent notifies, this operation
+ // is expected to be completed successfully.
+ let mut child_handle = execute_op_run_as_child(
+ TARGET_CTX,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ Uid::from_raw(uid1),
+ Gid::from_raw(gid1),
+ ForcedOp(false),
+ );
+
+ // Wait until (client#1) child process notifies us to continue, so that there will be a key
+ // generated by client#1.
+ child_handle.recv();
+
+ // Client#2: This child will try to load the key generated by client#1.
+ const APPLICATION_ID_2: u32 = 10602;
+ let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
+ let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
+ unsafe {
+ run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || {
+ let keystore2_inst = get_keystore_service();
+ let result =
+ key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+ });
+ };
+
+ // Notify the child process (client#1) to resume and finish.
+ child_handle.send(&BarrierReached {});
+ assert!(
+ (child_handle.get_result() == TestOutcome::Ok),
+ "Client#1 failed to complete the operation."
+ );
+}
+
+/// Generate EC key with BLOB as domain. Generated key should be returned to caller as key blob.
+/// Verify that `blob` field in the `KeyDescriptor` is not empty and should have the key blob.
+/// Try to use this key for performing a sample operation and the operation should complete
+/// successfully.
+#[test]
+fn keystore2_generate_key_with_blob_domain() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::BLOB,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ None,
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .unwrap();
+
+ assert!(key_metadata.certificate.is_some());
+ assert!(key_metadata.certificateChain.is_none());
+
+ // Must have the key blob.
+ assert!(key_metadata.key.blob.is_some());
+
+ let op_response = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
+ false,
+ ))
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ // Delete the generated key blob.
+ sec_level.deleteKey(&key_metadata.key).unwrap();
+}