diff --git a/keystore/binder/android/security/keystore/IKeystoreService.aidl b/keystore/binder/android/security/keystore/IKeystoreService.aidl index a7601388..ea1e0f46 100644 --- a/keystore/binder/android/security/keystore/IKeystoreService.aidl +++ b/keystore/binder/android/security/keystore/IKeystoreService.aidl @@ -75,4 +75,5 @@ interface IKeystoreService { 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 2f07fbf4..24bf3318 100644 --- a/keystore/key_store_service.cpp +++ b/keystore/key_store_service.cpp @@ -276,6 +276,79 @@ Status KeyStoreService::list(const String16& prefix, int32_t targetUid, 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* 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(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 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(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(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(ResponseCode::NO_ERROR); + return Status::ok(); +} + Status KeyStoreService::reset(int32_t* aidl_return) { if (!checkBinderPermission(P_RESET)) { *aidl_return = static_cast(ResponseCode::PERMISSION_DENIED); diff --git a/keystore/key_store_service.h b/keystore/key_store_service.h index 601ed211..5a3586f1 100644 --- a/keystore/key_store_service.h +++ b/keystore/key_store_service.h @@ -61,6 +61,9 @@ class KeyStoreService : public android::security::keystore::BnKeystoreService { 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* 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 0981f1eb..e1fb2d9c 100644 --- a/keystore/keystore_cli_v2.cpp +++ b/keystore/keystore_cli_v2.cpp @@ -66,6 +66,7 @@ void PrintUsageAndExit() { " delete-all\n" " exists --name=\n" " list [--prefix=]\n" + " list-apps-with-keys\n" " sign-verify --name=\n" " [en|de]crypt --name= --in= --out=\n" " [--seclevel=software|strongbox|tee(default)]\n" @@ -285,7 +286,8 @@ int AddEntropy(const std::string& input, int32_t flags) { 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 keystore = CreateKeystoreInstance(); AuthorizationSetBuilder params; params.RsaSigningKey(2048, 65537) @@ -294,8 +296,14 @@ int GenerateKey(const std::string& name, int32_t flags) { .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 @@ int List(const std::string& prefix) { return 0; } +int ListAppsWithKeys() { + + sp sm = android::defaultServiceManager(); + sp binder = sm->getService(String16("android.security.keystore")); + sp service = android::interface_cast(binder); + if (service == nullptr) { + fprintf(stderr, "Error connecting to keystore service.\n"); + return 1; + } + int32_t aidl_return; + ::std::vector 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 keystore = CreateKeystoreInstance(); AuthorizationSetBuilder sign_params; @@ -591,7 +628,8 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 00000000..f609b346 --- /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