Merge "Allow Device ID attestation profile owner"
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
new file mode 100644
index 0000000..e7cd09d
--- /dev/null
+++ b/keystore-engine/Android.bp
@@ -0,0 +1,74 @@
+// Copyright (C) 2012 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.
+
+cc_library_shared {
+    name: "libkeystore-engine",
+
+    srcs: [
+        "android_engine.cpp",
+        "keystore_backend_binder.cpp",
+    ],
+
+    cflags: [
+        "-fvisibility=hidden",
+        "-Wall",
+        "-Werror",
+    ],
+    cpp_std: "c++17",
+
+    shared_libs: [
+        "libbinder",
+        "libcrypto",
+        "libcutils",
+        "libhidlbase",
+        "libkeystore_aidl",
+        "libkeystore_binder",
+        "libkeystore_parcelables",
+        "liblog",
+        "libbase",
+        "libutils",
+    ],
+
+}
+
+// This builds a variant of libkeystore-engine that uses a HIDL HAL
+// owned by the WiFi user to perform signing operations.
+cc_library_shared {
+    name: "libkeystore-engine-wifi-hidl",
+
+    srcs: [
+        "android_engine.cpp",
+        "keystore_backend_hidl.cpp",
+    ],
+
+    cflags: [
+        "-fvisibility=hidden",
+        "-Wall",
+        "-Werror",
+        "-DBACKEND_WIFI_HIDL",
+    ],
+    cpp_std: "c++17",
+
+    shared_libs: [
+        "android.system.wifi.keystore@1.0",
+        "libcrypto",
+        "liblog",
+        "libhidlbase",
+        "libhidltransport",
+        "libcutils",
+        "libutils",
+    ],
+
+    vendor: true,
+}
diff --git a/keystore-engine/Android.mk b/keystore-engine/Android.mk
deleted file mode 100644
index e182dbd..0000000
--- a/keystore-engine/Android.mk
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright (C) 2012 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libkeystore-engine
-
-LOCAL_SRC_FILES := \
-	android_engine.cpp \
-	keystore_backend_binder.cpp
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := -fvisibility=hidden -Wall -Werror
-LOCAL_CPPFLAGS += -std=c++17
-
-LOCAL_SHARED_LIBRARIES += \
-	libbinder \
-	libcrypto \
-	libcutils \
-	libhidlbase \
-	libkeystore_aidl \
-	libkeystore_binder \
-	libkeystore_parcelables \
-	liblog \
-	libbase \
-	libutils
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
-
-# This builds a variant of libkeystore-engine that uses a HIDL HAL
-# owned by the WiFi user to perform signing operations.
-LOCAL_MODULE := libkeystore-engine-wifi-hidl
-
-LOCAL_SRC_FILES := \
-	android_engine.cpp \
-	keystore_backend_hidl.cpp
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := -fvisibility=hidden -Wall -Werror -DBACKEND_WIFI_HIDL
-LOCAL_CPPFLAGS += -std=c++17
-
-LOCAL_SHARED_LIBRARIES += \
-	android.system.wifi.keystore@1.0 \
-	libcrypto \
-	liblog \
-	libhidlbase \
-	libhidltransport \
-	libcutils \
-	libutils
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-LOCAL_VENDOR_MODULE := true
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/keystore-engine/keystore_backend_binder.cpp b/keystore-engine/keystore_backend_binder.cpp
index 0e38b50..f6ae7f5 100644
--- a/keystore-engine/keystore_backend_binder.cpp
+++ b/keystore-engine/keystore_backend_binder.cpp
@@ -25,6 +25,7 @@
 #include <android-base/logging.h>
 #include <android/security/keystore/IKeystoreService.h>
 #include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
 #include <keystore/KeyCharacteristics.h>
 #include <keystore/KeymasterArguments.h>
 #include <keystore/KeymasterBlob.h>
@@ -37,6 +38,7 @@
 #include <keystore/keystore_return_types.h>
 
 #include <future>
+#include <thread>
 
 using android::security::keystore::IKeystoreService;
 using namespace android;
@@ -87,6 +89,10 @@
     return {};
 }
 
+KeystoreBackendBinder::KeystoreBackendBinder() {
+    android::ProcessState::self()->startThreadPool();
+}
+
 int32_t KeystoreBackendBinder::sign(const char* key_id, const uint8_t* in, size_t len,
                                     uint8_t** reply, size_t* reply_len) {
     sp<IServiceManager> sm = defaultServiceManager();
diff --git a/keystore-engine/keystore_backend_binder.h b/keystore-engine/keystore_backend_binder.h
index 1db90f7..4c828c5 100644
--- a/keystore-engine/keystore_backend_binder.h
+++ b/keystore-engine/keystore_backend_binder.h
@@ -27,7 +27,7 @@
 
 class KeystoreBackendBinder : public KeystoreBackend {
   public:
-    KeystoreBackendBinder() {}
+    KeystoreBackendBinder();
     virtual ~KeystoreBackendBinder() {}
     int32_t sign(const char *key_id, const uint8_t* in, size_t len,
                  uint8_t** reply, size_t* reply_len) override;
diff --git a/keystore/KeyStore.cpp b/keystore/KeyStore.cpp
index 7530243..ac3ab5f 100644
--- a/keystore/KeyStore.cpp
+++ b/keystore/KeyStore.cpp
@@ -138,21 +138,13 @@
     android::String8 prefix("");
     android::Vector<android::String16> aliases;
 
-    // DO NOT
-    // move
-    // auto userState = userStateDB_.getUserState(userId);
-    // here, in an attempt to replace userStateDB_.getUserState(userId) with userState.
-    // userState is a proxy that holds a lock which may required by a worker.
-    // LockedKeyBlobEntry::list has a fence that waits until all workers have finished which may
-    // not happen if a user state lock is held. The following line only briefly grabs the lock.
-    // Grabbing the user state lock after the list call is also save since workers cannot grab
-    // blob entry locks.
-
     auto userState = mUserStateDB.getUserState(userId);
     std::string userDirName = userState->getUserDirName();
     auto encryptionKey = userState->getEncryptionKey();
     auto state = userState->getState();
-    // unlock the user state
+    // userState is a proxy that holds a lock which may be required by a worker.
+    // LockedKeyBlobEntry::list has a fence that waits until all workers have finished which may
+    // not happen if a user state lock is held. The following line relinquishes the lock.
     userState = {};
 
     ResponseCode rc;
@@ -217,7 +209,7 @@
 bool KeyStore::isEmpty(uid_t userId) const {
     std::string userDirName;
     {
-        // userState hold a lock which must be relinqhished before list is called. This scope
+        // userState holds a lock which must be relinquished before list is called. This scope
         // prevents deadlocks.
         auto userState = mUserStateDB.getUserState(userId);
         if (!userState) {
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/blob.h b/keystore/blob.h
index 5cd1b90..a7f9fd0 100644
--- a/keystore/blob.h
+++ b/keystore/blob.h
@@ -196,10 +196,10 @@
     bool hasCharacteristicsBlob() const;
 
     bool operator<(const KeyBlobEntry& rhs) const {
-        return std::tie(alias_, user_dir_, uid_) < std::tie(rhs.alias_, rhs.user_dir_, uid_);
+        return std::tie(uid_, alias_, user_dir_) < std::tie(rhs.uid_, rhs.alias_, rhs.user_dir_);
     }
     bool operator==(const KeyBlobEntry& rhs) const {
-        return std::tie(alias_, user_dir_, uid_) == std::tie(rhs.alias_, rhs.user_dir_, uid_);
+        return std::tie(uid_, alias_, user_dir_) == std::tie(rhs.uid_, rhs.alias_, rhs.user_dir_);
     }
     bool operator!=(const KeyBlobEntry& rhs) const { return !(*this == rhs); }
 
diff --git a/keystore/key_store_service.cpp b/keystore/key_store_service.cpp
index 76db1d9..0039ea4 100644
--- a/keystore/key_store_service.cpp
+++ b/keystore/key_store_service.cpp
@@ -254,10 +254,10 @@
 
     ResponseCode rc;
     std::list<LockedKeyBlobEntry> internal_matches;
+    auto userDirName = mKeyStore->getUserStateDB().getUserStateByUid(targetUid)->getUserDirName();
 
-    std::tie(rc, internal_matches) = LockedKeyBlobEntry::list(
-        mKeyStore->getUserStateDB().getUserStateByUid(targetUid)->getUserDirName(),
-        [&](uid_t uid, const std::string& alias) {
+    std::tie(rc, internal_matches) =
+        LockedKeyBlobEntry::list(userDirName, [&](uid_t uid, const std::string& alias) {
             std::mismatch(stdPrefix.begin(), stdPrefix.end(), alias.begin(), alias.end());
             return uid == static_cast<uid_t>(targetUid) &&
                    std::mismatch(stdPrefix.begin(), stdPrefix.end(), alias.begin(), alias.end())
@@ -274,6 +274,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);
@@ -507,11 +580,10 @@
     return Status::ok();
 }
 
-Status KeyStoreService::clear_uid(int64_t targetUid64, int32_t* aidl_return) {
+Status KeyStoreService::clear_uid(int64_t targetUid64, int32_t* _aidl_return) {
     uid_t targetUid = getEffectiveUid(targetUid64);
     if (!checkBinderPermissionSelfOrSystem(P_CLEAR_UID, targetUid)) {
-        *aidl_return = static_cast<int32_t>(ResponseCode::PERMISSION_DENIED);
-        return Status::ok();
+        return AIDL_RETURN(ResponseCode::PERMISSION_DENIED);
     }
     ALOGI("clear_uid %" PRId64, targetUid64);
 
@@ -519,16 +591,15 @@
 
     ResponseCode rc;
     std::list<LockedKeyBlobEntry> entries;
+    auto userDirName = mKeyStore->getUserStateDB().getUserStateByUid(targetUid)->getUserDirName();
 
     // list has a fence making sure no workers are modifying blob files before iterating the
     // data base. All returned entries are locked.
     std::tie(rc, entries) = LockedKeyBlobEntry::list(
-        mKeyStore->getUserStateDB().getUserStateByUid(targetUid)->getUserDirName(),
-        [&](uid_t uid, const std::string&) -> bool { return uid == targetUid; });
+        userDirName, [&](uid_t uid, const std::string&) -> bool { return uid == targetUid; });
 
     if (rc != ResponseCode::NO_ERROR) {
-        *aidl_return = static_cast<int32_t>(rc);
-        return Status::ok();
+        return AIDL_RETURN(rc);
     }
 
     for (LockedKeyBlobEntry& lockedEntry : entries) {
@@ -543,8 +614,7 @@
         }
         mKeyStore->del(lockedEntry);
     }
-    *aidl_return = static_cast<int32_t>(ResponseCode::NO_ERROR);
-    return Status::ok();
+    return AIDL_RETURN(ResponseCode::NO_ERROR);
 }
 
 Status KeyStoreService::addRngEntropy(
@@ -746,7 +816,7 @@
 
     std::tie(rc, keyBlob, charBlob, lockedEntry) =
         mKeyStore->getKeyForName(name8, targetUid, TYPE_KEYMASTER_10);
-    if (!rc) {
+    if (!rc.isOk()) {
         return AIDL_RETURN(rc);
     }
 
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/keymaster_worker.cpp b/keystore/keymaster_worker.cpp
index f3bf71f..6dc055f 100644
--- a/keystore/keymaster_worker.cpp
+++ b/keystore/keymaster_worker.cpp
@@ -88,6 +88,8 @@
     std::tie(rc, blob, charBlob) =
         lockedEntry.readBlobs(userState->getEncryptionKey(), userState->getState());
 
+    userState = {};
+
     if (rc != ResponseCode::NO_ERROR) {
         return error = rc, result;
     }
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 1c94318..777db33 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -68,6 +68,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"
@@ -287,7 +288,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)
@@ -296,8 +298,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,
@@ -366,6 +374,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;
@@ -593,8 +630,7 @@
     CommandLine* command_line = CommandLine::ForCurrentProcess();
     CommandLine::StringVector args = command_line->GetArgs();
 
-    std::thread thread_pool([] { android::IPCThreadState::self()->joinThreadPool(false); });
-    thread_pool.detach();
+    android::ProcessState::self()->startThreadPool();
 
     if (args.empty()) {
         PrintUsageAndExit();
@@ -609,7 +645,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") {
@@ -622,6 +659,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