Added listUidsOfCredentialBoundKeys method to AIDL
Bug: b/112321280
Test: add auth bound keys from an app
Test: adb shell "su 1000 keystore_cli_v2 list-apps-with-keys"
Test: see uid of app listed
Test: $ ./keystore/tests/list_auth_bound_keys_test.sh
Change-Id: Id6739ea17e566f9aa51f1bd2d5a0715f1020b644
diff --git a/keystore/binder/android/security/keystore/IKeystoreService.aidl b/keystore/binder/android/security/keystore/IKeystoreService.aidl
index a760138..ea1e0f4 100644
--- a/keystore/binder/android/security/keystore/IKeystoreService.aidl
+++ b/keystore/binder/android/security/keystore/IKeystoreService.aidl
@@ -75,4 +75,5 @@
int cancelConfirmationPrompt(IBinder listener);
boolean isConfirmationPromptSupported();
int onKeyguardVisibilityChanged(in boolean isShowing, in int userId);
+ int listUidsOfAuthBoundKeys(out int[] uids);
}
diff --git a/keystore/key_store_service.cpp b/keystore/key_store_service.cpp
index 2f07fbf..24bf331 100644
--- a/keystore/key_store_service.cpp
+++ b/keystore/key_store_service.cpp
@@ -276,6 +276,79 @@
return Status::ok();
}
+/*
+ * This method will return the uids of all auth bound keys for the calling user.
+ * This is intended to be used for alerting the user about which apps will be affected
+ * if the password/pin is removed. Only allowed to be called by system.
+ * The output is bound by the initial size of uidsOut to be compatible with Java.
+ */
+Status KeyStoreService::listUidsOfAuthBoundKeys(::std::vector<int32_t>* uidsOut,
+ int32_t* aidl_return) {
+ const int32_t callingUid = IPCThreadState::self()->getCallingUid();
+ const int32_t userId = get_user_id(callingUid);
+ const int32_t appId = get_app_id(callingUid);
+ if (appId != AID_SYSTEM) {
+ ALOGE("Permission listUidsOfAuthBoundKeys denied for aid %d", appId);
+ *aidl_return = static_cast<int32_t>(ResponseCode::PERMISSION_DENIED);
+ return Status::ok();
+ }
+
+ const String8 prefix8("");
+ auto userState = mKeyStore->getUserStateDB().getUserState(userId);
+ const std::string userDirName = userState->getUserDirName();
+ auto encryptionKey = userState->getEncryptionKey();
+ auto state = userState->getState();
+ // unlock the user state
+ userState = {};
+
+ ResponseCode rc;
+ std::list<LockedKeyBlobEntry> internal_matches;
+ std::tie(rc, internal_matches) =
+ LockedKeyBlobEntry::list(userDirName, [&](uid_t, const std::string&) {
+ // Need to filter on auth bound state, so just return true.
+ return true;
+ });
+ if (rc != ResponseCode::NO_ERROR) {
+ ALOGE("Error listing blob entries for user %d", userId);
+ return Status::fromServiceSpecificError(static_cast<int32_t>(rc));
+ }
+
+ auto it = uidsOut->begin();
+ for (LockedKeyBlobEntry& entry : internal_matches) {
+ if (it == uidsOut->end()) {
+ ALOGW("Maximum number (%d) of auth bound uids found, truncating remainder",
+ static_cast<int32_t>(uidsOut->capacity()));
+ break;
+ }
+ if (std::find(uidsOut->begin(), it, entry->uid()) != it) {
+ // uid already in list, skip
+ continue;
+ }
+
+ auto [rc, blob, charBlob] = entry.readBlobs(encryptionKey, state);
+ if (rc != ResponseCode::NO_ERROR && rc != ResponseCode::LOCKED) {
+ ALOGE("Error reading blob for key %s", entry->alias().c_str());
+ continue;
+ }
+
+ if (blob && blob.isEncrypted()) {
+ *it++ = entry->uid();
+ } else if (charBlob) {
+ auto [success, hwEnforced, swEnforced] = charBlob.getKeyCharacteristics();
+ if (!success) {
+ ALOGE("Error reading blob characteristics for key %s", entry->alias().c_str());
+ continue;
+ }
+ if (hwEnforced.Contains(TAG_USER_SECURE_ID) ||
+ swEnforced.Contains(TAG_USER_SECURE_ID)) {
+ *it++ = entry->uid();
+ }
+ }
+ }
+ *aidl_return = static_cast<int32_t>(ResponseCode::NO_ERROR);
+ return Status::ok();
+}
+
Status KeyStoreService::reset(int32_t* aidl_return) {
if (!checkBinderPermission(P_RESET)) {
*aidl_return = static_cast<int32_t>(ResponseCode::PERMISSION_DENIED);
diff --git a/keystore/key_store_service.h b/keystore/key_store_service.h
index 601ed21..5a3586f 100644
--- a/keystore/key_store_service.h
+++ b/keystore/key_store_service.h
@@ -61,6 +61,9 @@
int32_t* _aidl_return) override;
::android::binder::Status list(const ::android::String16& namePrefix, int32_t uid,
::std::vector<::android::String16>* _aidl_return) override;
+ ::android::binder::Status listUidsOfAuthBoundKeys(::std::vector<int32_t>* uids,
+ int32_t* _aidl_return) override;
+
::android::binder::Status reset(int32_t* _aidl_return) override;
::android::binder::Status onUserPasswordChanged(int32_t userId,
const ::android::String16& newPassword,
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 0981f1e..e1fb2d9 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -66,6 +66,7 @@
" delete-all\n"
" exists --name=<key_name>\n"
" list [--prefix=<key_name_prefix>]\n"
+ " list-apps-with-keys\n"
" sign-verify --name=<key_name>\n"
" [en|de]crypt --name=<key_name> --in=<file> --out=<file>\n"
" [--seclevel=software|strongbox|tee(default)]\n"
@@ -285,7 +286,8 @@
return result;
}
-int GenerateKey(const std::string& name, int32_t flags) {
+// Note: auth_bound keys created with this tool will not be usable.
+int GenerateKey(const std::string& name, int32_t flags, bool auth_bound) {
std::unique_ptr<KeystoreClient> keystore = CreateKeystoreInstance();
AuthorizationSetBuilder params;
params.RsaSigningKey(2048, 65537)
@@ -294,8 +296,14 @@
.Digest(Digest::SHA_2_384)
.Digest(Digest::SHA_2_512)
.Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
- .Padding(PaddingMode::RSA_PSS)
- .Authorization(TAG_NO_AUTH_REQUIRED);
+ .Padding(PaddingMode::RSA_PSS);
+ if (auth_bound) {
+ // Gatekeeper normally generates the secure user id.
+ // Using zero allows the key to be created, but it will not be usuable.
+ params.Authorization(TAG_USER_SECURE_ID, 0);
+ } else {
+ params.Authorization(TAG_NO_AUTH_REQUIRED);
+ }
AuthorizationSet hardware_enforced_characteristics;
AuthorizationSet software_enforced_characteristics;
auto result = keystore->generateKey(name, params, flags, &hardware_enforced_characteristics,
@@ -364,6 +372,35 @@
return 0;
}
+int ListAppsWithKeys() {
+
+ sp<android::IServiceManager> sm = android::defaultServiceManager();
+ sp<android::IBinder> binder = sm->getService(String16("android.security.keystore"));
+ sp<IKeystoreService> service = android::interface_cast<IKeystoreService>(binder);
+ if (service == nullptr) {
+ fprintf(stderr, "Error connecting to keystore service.\n");
+ return 1;
+ }
+ int32_t aidl_return;
+ ::std::vector<int32_t> uids(100);
+ android::binder::Status status = service->listUidsOfAuthBoundKeys(&uids, &aidl_return);
+ if (!status.isOk()) {
+ fprintf(stderr, "Requesting uids of auth bound keys failed with error %s.\n",
+ status.toString8().c_str());
+ return 1;
+ }
+ if (!KeyStoreNativeReturnCode(aidl_return).isOk()) {
+ fprintf(stderr, "Requesting uids of auth bound keys failed with code %d.\n", aidl_return);
+ return 1;
+ }
+ printf("Apps with auth bound keys:\n");
+ for (auto i = uids.begin(); i != uids.end(); ++i) {
+ if (*i == 0) break;
+ printf("%d\n", *i);
+ }
+ return 0;
+}
+
int SignAndVerify(const std::string& name) {
std::unique_ptr<KeystoreClient> keystore = CreateKeystoreInstance();
AuthorizationSetBuilder sign_params;
@@ -591,7 +628,8 @@
securityLevelOption2Flags(*command_line));
} else if (args[0] == "generate") {
return GenerateKey(command_line->GetSwitchValueASCII("name"),
- securityLevelOption2Flags(*command_line));
+ securityLevelOption2Flags(*command_line),
+ command_line->HasSwitch("auth_bound"));
} else if (args[0] == "get-chars") {
return GetCharacteristics(command_line->GetSwitchValueASCII("name"));
} else if (args[0] == "export") {
@@ -604,6 +642,8 @@
return DoesKeyExist(command_line->GetSwitchValueASCII("name"));
} else if (args[0] == "list") {
return List(command_line->GetSwitchValueASCII("prefix"));
+ } else if (args[0] == "list-apps-with-keys") {
+ return ListAppsWithKeys();
} else if (args[0] == "sign-verify") {
return SignAndVerify(command_line->GetSwitchValueASCII("name"));
} else if (args[0] == "encrypt") {
diff --git a/keystore/tests/list_auth_bound_keys_test.sh b/keystore/tests/list_auth_bound_keys_test.sh
new file mode 100755
index 0000000..f609b34
--- /dev/null
+++ b/keystore/tests/list_auth_bound_keys_test.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+
+#
+# Copyright (C) 2018 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.
+#
+#
+# Simple adb based test for keystore method list_auth_bound_keys
+# Depends on keystore_cli_v2 tool and root
+#
+
+set -e
+
+ROOT_ID=0
+USER1_ID=10901
+USER2_ID=10902
+SYSTEM_ID=1000
+
+function cli {
+ adb shell "su $1 keystore_cli_v2 $2"
+}
+
+#start as root
+adb root
+
+# generate keys as user
+echo "generating keys"
+cli $USER1_ID "delete --name=no_auth_key" || true
+cli $USER1_ID "generate --name=no_auth_key"
+cli $USER2_ID "delete --name=auth_key" || true
+if ! cli $USER2_ID "generate --name=auth_key --auth_bound"; then
+ echo "Unable to generate auth bound key, make sure device/emulator has a pin/password set."
+ echo "$ adb shell locksettings set-pin 1234"
+ exit 1
+fi
+
+# try to list keys as user
+if cli $USER2_ID list-apps-with-keys; then
+ echo "Error: list-apps-with-keys succeeded as user, this is not expected!"
+ exit 1
+fi
+
+# try to list keys as root
+if cli $ROOT_ID "list-apps-with-keys"; then
+ echo "Error: list-apps-with-keys succeeded as root, this is not expected!"
+ exit 1
+fi
+
+# try to list keys as system
+success=false
+while read -r line; do
+ echo $line
+ if [ "$line" == "$USER2_ID" ]; then
+ success=true
+ fi
+ if [ "$line" == "$USER1_ID" ]; then
+ echo "Error: User1 id not expected in list"
+ exit 1
+ fi
+done <<< $(cli $SYSTEM_ID "list-apps-with-keys")
+if [ $success = true ]; then
+ echo "Success!"
+else
+ echo "Error: User2 id not in list"
+ exit 1
+fi
\ No newline at end of file