Merge "Keystore 2.0: Cleanup"
diff --git a/Android.bp b/Android.bp
index b44c296..4a0253c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1 +1,31 @@
+package {
+ default_applicable_licenses: ["system_security_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "system_security_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
subdirs = ["*"]
diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp
index 3c9ade0..39d4e6b 100644
--- a/fsverity_init/Android.bp
+++ b/fsverity_init/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_binary {
name: "fsverity_init",
srcs: [
diff --git a/identity/Android.bp b/identity/Android.bp
index e6d77c8..ed8ff2f 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_defaults {
name: "identity_defaults",
cflags: [
diff --git a/identity/Credential.cpp b/identity/Credential.cpp
index 4a2bae1..a3c72ed 100644
--- a/identity/Credential.cpp
+++ b/identity/Credential.cpp
@@ -117,26 +117,42 @@
"Error loading data for credential");
}
- selectedAuthKey_ = data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
- if (selectedAuthKey_ == nullptr) {
+ // We just check if a key is available, we actually don't store it since we
+ // don't keep CredentialData around between binder calls.
+ const AuthKeyData* authKey =
+ data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
+ if (authKey == nullptr) {
return Status::fromServiceSpecificError(
ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
"No suitable authentication key available");
}
+ if (!ensureChallenge()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error getting challenge (bug in HAL or TA)");
+ }
+ *_aidl_return = selectedChallenge_;
+ return Status::ok();
+}
+
+bool Credential::ensureChallenge() {
+ if (selectedChallenge_ != 0) {
+ return true;
+ }
+
int64_t challenge;
Status status = halBinder_->createAuthChallenge(&challenge);
if (!status.isOk()) {
- return halStatusToGenericError(status);
+ LOG(ERROR) << "Error getting challenge: " << status.exceptionMessage();
+ return false;
}
if (challenge == 0) {
- return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
- "Returned challenge is 0 (bug in HAL or TA)");
+ LOG(ERROR) << "Returned challenge is 0 (bug in HAL or TA)";
+ return false;
}
selectedChallenge_ = challenge;
- *_aidl_return = challenge;
- return Status::ok();
+ return true;
}
class CredstoreTokenCallback : public android::security::keystore::BnCredstoreTokenCallback,
@@ -279,13 +295,6 @@
}
}
- // If requesting a challenge-based authToken the idea is that authentication
- // happens as part of the transaction. As such, authTokenMaxAgeMillis should
- // be nearly zero. We'll use 10 seconds for this.
- if (userAuthNeeded && selectedChallenge_ != 0) {
- authTokenMaxAgeMillis = 10 * 1000;
- }
-
// Reset tokens and only get them if they're actually needed, e.g. if user authentication
// is needed in any of the access control profiles for data items being requested.
//
@@ -303,6 +312,28 @@
aidlVerificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE;
aidlVerificationToken.mac.clear();
if (userAuthNeeded) {
+ // If user authentication is needed, always get a challenge from the
+ // HAL/TA since it'll need it to check the returned VerificationToken
+ // for freshness.
+ if (!ensureChallenge()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error getting challenge (bug in HAL or TA)");
+ }
+
+ // Note: if all selected profiles require auth-on-every-presentation
+ // then authTokenMaxAgeMillis will be 0 (because timeoutMillis for each
+ // profile is 0). Which means that keystore will only return an
+ // AuthToken if its challenge matches what we pass, regardless of its
+ // age. This is intended b/c the HAL/TA will check not care about
+ // the age in this case, it only cares that the challenge matches.
+ //
+ // Otherwise, if one or more of the profiles is auth-with-a-timeout then
+ // authTokenMaxAgeMillis will be set to the largest of those
+ // timeouts. We'll get an AuthToken which satisfies this deadline if it
+ // exists. This authToken _may_ have the requested challenge but it's
+ // not a guarantee and it's also not required.
+ //
+
vector<uint8_t> authTokenBytes;
vector<uint8_t> verificationTokenBytes;
if (!getTokensFromKeystore(selectedChallenge_, data->getSecureUserId(),
@@ -320,6 +351,7 @@
if (authTokenBytes.size() > 0) {
HardwareAuthToken authToken =
android::hardware::keymaster::V4_0::support::hidlVec2AuthToken(authTokenBytes);
+
// Convert from HIDL to AIDL...
aidlAuthToken.challenge = int64_t(authToken.challenge);
aidlAuthToken.userId = int64_t(authToken.userId);
@@ -351,15 +383,25 @@
// Note that the selectAuthKey() method is only called if a CryptoObject is involved at
// the Java layer. So we could end up with no previously selected auth key and we may
// need one.
- const AuthKeyData* authKey = selectedAuthKey_;
- if (sessionTranscript.size() > 0) {
- if (authKey == nullptr) {
- authKey = data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
- if (authKey == nullptr) {
- return Status::fromServiceSpecificError(
- ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
- "No suitable authentication key available");
- }
+ //
+ const AuthKeyData* authKey =
+ data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
+ if (authKey == nullptr) {
+ // If no authKey is available, consider it an error only when a
+ // SessionTranscript was provided.
+ //
+ // We allow no SessionTranscript to be provided because it makes
+ // the API simpler to deal with insofar it can be used without having
+ // to generate any authentication keys.
+ //
+ // In this "no SessionTranscript is provided" mode we don't return
+ // DeviceNameSpaces nor a MAC over DeviceAuthentication so we don't
+ // need a device key.
+ //
+ if (sessionTranscript.size() > 0) {
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
+ "No suitable authentication key available and one is needed");
}
}
vector<uint8_t> signingKeyBlob;
@@ -750,31 +792,36 @@
//
// It is because of this we need to set the CredentialKey certificate chain,
// keyCount, and maxUsesPerKey below.
- sp<WritableCredential> writableCredential =
- new WritableCredential(dataPath_, credentialName_, docType.value(), true, hwInfo_,
- halWritableCredential, halApiVersion_);
+ sp<WritableCredential> writableCredential = new WritableCredential(
+ dataPath_, credentialName_, docType.value(), true, hwInfo_, halWritableCredential);
writableCredential->setAttestationCertificate(data->getAttestationCertificate());
auto [keyCount, maxUsesPerKey] = data->getAvailableAuthenticationKeys();
writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
- // Because its data has changed, we need to reconnect to the HAL when the
- // credential has been updated... otherwise the remote object will have
- // stale data for future calls (e.g. getAuthKeysNeedingCertification().
+ // Because its data has changed, we need to replace the binder for the
+ // IIdentityCredential when the credential has been updated... otherwise the
+ // remote object will have stale data for future calls, for example
+ // getAuthKeysNeedingCertification().
//
- // The joys and pitfalls of mutable objects...
+ // The way this is implemented is that setCredentialToReloadWhenUpdated()
+ // instructs the WritableCredential to call writableCredentialPersonalized()
+ // on |this|.
//
- writableCredential->setCredentialUpdatedCallback([this] {
- Status status = this->ensureOrReplaceHalBinder();
- if (!status.isOk()) {
- LOG(ERROR) << "Error loading credential";
- }
- });
+ //
+ writableCredential->setCredentialToReloadWhenUpdated(this);
*_aidl_return = writableCredential;
return Status::ok();
}
+void Credential::writableCredentialPersonalized() {
+ Status status = ensureOrReplaceHalBinder();
+ if (!status.isOk()) {
+ LOG(ERROR) << "Error reloading credential";
+ }
+}
+
} // namespace identity
} // namespace security
} // namespace android
diff --git a/identity/Credential.h b/identity/Credential.h
index 7f08515..a76f3cc 100644
--- a/identity/Credential.h
+++ b/identity/Credential.h
@@ -50,6 +50,7 @@
~Credential();
Status ensureOrReplaceHalBinder();
+ void writableCredentialPersonalized();
// ICredential overrides
Status createEphemeralKeyPair(vector<uint8_t>* _aidl_return) override;
@@ -94,12 +95,13 @@
HardwareInformation hwInfo_;
sp<IIdentityCredentialStore> halStoreBinder_;
- const AuthKeyData* selectedAuthKey_ = nullptr;
uint64_t selectedChallenge_ = 0;
sp<IIdentityCredential> halBinder_;
int halApiVersion_;
+ bool ensureChallenge();
+
ssize_t
calcExpectedDeviceNameSpacesSize(const vector<uint8_t>& requestMessage,
const vector<RequestNamespaceParcel>& requestNamespaces,
diff --git a/identity/CredentialData.h b/identity/CredentialData.h
index b037997..24b55d3 100644
--- a/identity/CredentialData.h
+++ b/identity/CredentialData.h
@@ -55,7 +55,7 @@
vector<uint8_t> certificate;
vector<uint8_t> keyBlob;
- int64_t expirationDateMillisSinceEpoch;
+ int64_t expirationDateMillisSinceEpoch = 0;
vector<uint8_t> staticAuthenticationData;
vector<uint8_t> pendingCertificate;
vector<uint8_t> pendingKeyBlob;
diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp
index f77294e..509e022 100644
--- a/identity/CredentialStore.cpp
+++ b/identity/CredentialStore.cpp
@@ -90,7 +90,7 @@
}
sp<IWritableCredential> writableCredential = new WritableCredential(
- dataPath_, credentialName, docType, false, hwInfo_, halWritableCredential, halApiVersion_);
+ dataPath_, credentialName, docType, false, hwInfo_, halWritableCredential);
*_aidl_return = writableCredential;
return Status::ok();
}
diff --git a/identity/WritableCredential.cpp b/identity/WritableCredential.cpp
index d0688b8..a300e51 100644
--- a/identity/WritableCredential.cpp
+++ b/identity/WritableCredential.cpp
@@ -41,15 +41,14 @@
WritableCredential::WritableCredential(const string& dataPath, const string& credentialName,
const string& docType, bool isUpdate,
HardwareInformation hwInfo,
- sp<IWritableIdentityCredential> halBinder, int halApiVersion)
+ sp<IWritableIdentityCredential> halBinder)
: dataPath_(dataPath), credentialName_(credentialName), docType_(docType), isUpdate_(isUpdate),
- hwInfo_(std::move(hwInfo)), halBinder_(halBinder), halApiVersion_(halApiVersion) {}
+ hwInfo_(std::move(hwInfo)), halBinder_(halBinder) {}
WritableCredential::~WritableCredential() {}
-void WritableCredential::setCredentialUpdatedCallback(
- std::function<void()>&& onCredentialUpdatedCallback) {
- onCredentialUpdatedCallback_ = onCredentialUpdatedCallback;
+void WritableCredential::setCredentialToReloadWhenUpdated(sp<Credential> credential) {
+ credentialToReloadWhenUpdated_ = credential;
}
Status WritableCredential::ensureAttestationCertificateExists(const vector<uint8_t>& challenge) {
@@ -268,7 +267,10 @@
"Error saving credential data to disk");
}
- onCredentialUpdatedCallback_();
+ if (credentialToReloadWhenUpdated_) {
+ credentialToReloadWhenUpdated_->writableCredentialPersonalized();
+ credentialToReloadWhenUpdated_.clear();
+ }
*_aidl_return = proofOfProvisioningSignature;
return Status::ok();
diff --git a/identity/WritableCredential.h b/identity/WritableCredential.h
index 6ff31ae..838b956 100644
--- a/identity/WritableCredential.h
+++ b/identity/WritableCredential.h
@@ -24,6 +24,8 @@
#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include "Credential.h"
+
namespace android {
namespace security {
namespace identity {
@@ -38,13 +40,15 @@
public:
WritableCredential(const string& dataPath, const string& credentialName, const string& docType,
bool isUpdate, HardwareInformation hwInfo,
- sp<IWritableIdentityCredential> halBinder, int halApiVersion);
+ sp<IWritableIdentityCredential> halBinder);
~WritableCredential();
// Used when updating a credential
void setAttestationCertificate(const vector<uint8_t>& attestationCertificate);
void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
- void setCredentialUpdatedCallback(std::function<void()>&& onCredentialUpdatedCallback);
+
+ // Used by Credential::update()
+ void setCredentialToReloadWhenUpdated(sp<Credential> credential);
// IWritableCredential overrides
Status getCredentialKeyCertificateChain(const vector<uint8_t>& challenge,
@@ -61,13 +65,12 @@
bool isUpdate_;
HardwareInformation hwInfo_;
sp<IWritableIdentityCredential> halBinder_;
- int halApiVersion_;
vector<uint8_t> attestationCertificate_;
int keyCount_ = 0;
int maxUsesPerKey_ = 1;
- std::function<void()> onCredentialUpdatedCallback_ = []() {};
+ sp<Credential> credentialToReloadWhenUpdated_;
ssize_t calcExpectedProofOfProvisioningSize(
const vector<AccessControlProfileParcel>& accessControlProfiles,
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
index 6512c66..9980765 100644
--- a/keystore-engine/Android.bp
+++ b/keystore-engine/Android.bp
@@ -12,12 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_library_shared {
name: "libkeystore-engine",
srcs: [
"android_engine.cpp",
"keystore_backend_binder.cpp",
+ "keystore2_engine.cpp",
],
cflags: [
@@ -27,7 +37,9 @@
],
shared_libs: [
+ "android.system.keystore2-V1-ndk_platform",
"libbinder",
+ "libbinder_ndk",
"libcrypto",
"libcutils",
"libhidlbase",
@@ -49,6 +61,7 @@
srcs: [
"android_engine.cpp",
"keystore_backend_hidl.cpp",
+ "keystore2_engine.cpp",
],
cflags: [
@@ -59,7 +72,10 @@
],
shared_libs: [
+ "android.system.keystore2-V1-ndk_platform",
"android.system.wifi.keystore@1.0",
+ "libbase",
+ "libbinder_ndk",
"libcrypto",
"liblog",
"libhidlbase",
diff --git a/keystore-engine/android_engine.cpp b/keystore-engine/android_engine.cpp
index e3525b2..5881523 100644
--- a/keystore-engine/android_engine.cpp
+++ b/keystore-engine/android_engine.cpp
@@ -23,10 +23,7 @@
#define LOG_TAG "keystore-engine"
#include <pthread.h>
-#include <sys/socket.h>
-#include <stdarg.h>
#include <string.h>
-#include <unistd.h>
#include <log/log.h>
@@ -41,6 +38,8 @@
#include <memory>
+#include "keystore2_engine.h"
+
#ifndef BACKEND_WIFI_HIDL
#include "keystore_backend_binder.h"
#else
@@ -335,6 +334,10 @@
EVP_PKEY* EVP_PKEY_from_keystore(const char* key_id) {
ALOGV("EVP_PKEY_from_keystore(\"%s\")", key_id);
+ if (auto ks2_key = EVP_PKEY_from_keystore2(key_id)) {
+ return ks2_key;
+ }
+
ensure_keystore_engine();
uint8_t *pubkey = nullptr;
diff --git a/keystore-engine/keystore2_engine.cpp b/keystore-engine/keystore2_engine.cpp
new file mode 100644
index 0000000..8d25f48
--- /dev/null
+++ b/keystore-engine/keystore2_engine.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "keystore2_engine.h"
+
+#include <aidl/android/system/keystore2/IKeystoreService.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android/binder_manager.h>
+
+#include <private/android_filesystem_config.h>
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ec_key.h>
+#include <openssl/ecdsa.h>
+#include <openssl/engine.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#define AT __func__ << ":" << __LINE__ << " "
+
+constexpr const char keystore2_service_name[] = "android.system.keystore2";
+const std::string keystore2_grant_id_prefix("ks2_keystore-engine_grant_id:");
+
+/**
+ * Keystore 2.0 namespace identifiers.
+ * Keep in sync with system/sepolicy/private/keystore2_key_contexts.
+ */
+constexpr const int64_t KS2_NAMESPACE_WIFI = 102;
+
+namespace ks2 = ::aidl::android::system::keystore2;
+namespace KMV1 = ::aidl::android::hardware::security::keymint;
+
+namespace {
+
+int64_t getNamespaceforCurrentUid() {
+ auto uid = getuid();
+ switch (uid) {
+ case AID_WIFI:
+ return KS2_NAMESPACE_WIFI;
+ // 0 is the super user namespace, and nothing has access to this namespace on user builds.
+ // So this will always fail.
+ default:
+ return 0;
+ }
+}
+
+struct Keystore2KeyBackend {
+ ks2::KeyDescriptor descriptor_;
+ std::shared_ptr<ks2::IKeystoreSecurityLevel> i_keystore_security_level_;
+};
+
+/* key_backend_dup is called when one of the RSA or EC_KEY objects is duplicated. */
+extern "C" int key_backend_dup(CRYPTO_EX_DATA* /* to */, const CRYPTO_EX_DATA* /* from */,
+ void** from_d, int /* index */, long /* argl */, void* /* argp */) {
+ auto key_backend = reinterpret_cast<std::shared_ptr<Keystore2KeyBackend>*>(*from_d);
+ if (key_backend != nullptr) {
+ *from_d = new std::shared_ptr<Keystore2KeyBackend>(*key_backend);
+ }
+ return 1;
+}
+
+/* key_backend_free is called when one of the RSA, DSA or EC_KEY object is freed. */
+extern "C" void key_backend_free(void* /* parent */, void* ptr, CRYPTO_EX_DATA* /* ad */,
+ int /* index */, long /* argl */, void* /* argp */) {
+ delete reinterpret_cast<std::shared_ptr<Keystore2KeyBackend>*>(ptr);
+}
+
+extern "C" int rsa_private_transform(RSA* rsa, uint8_t* out, const uint8_t* in, size_t len);
+extern "C" int ecdsa_sign(const uint8_t* digest, size_t digest_len, uint8_t* sig,
+ unsigned int* sig_len, EC_KEY* ec_key);
+/* KeystoreEngine is a BoringSSL ENGINE that implements RSA and ECDSA by
+ * forwarding the requested operations to Keystore. */
+class Keystore2Engine {
+ public:
+ Keystore2Engine()
+ : rsa_index_(RSA_get_ex_new_index(0 /* argl */, nullptr /* argp */, nullptr /* new_func */,
+ key_backend_dup, key_backend_free)),
+ ec_key_index_(EC_KEY_get_ex_new_index(0 /* argl */, nullptr /* argp */,
+ nullptr /* new_func */, key_backend_dup,
+ key_backend_free)),
+ engine_(ENGINE_new()) {
+ memset(&rsa_method_, 0, sizeof(rsa_method_));
+ rsa_method_.common.is_static = 1;
+ rsa_method_.private_transform = rsa_private_transform;
+ rsa_method_.flags = RSA_FLAG_OPAQUE;
+ ENGINE_set_RSA_method(engine_, &rsa_method_, sizeof(rsa_method_));
+
+ memset(&ecdsa_method_, 0, sizeof(ecdsa_method_));
+ ecdsa_method_.common.is_static = 1;
+ ecdsa_method_.sign = ecdsa_sign;
+ ecdsa_method_.flags = ECDSA_FLAG_OPAQUE;
+ ENGINE_set_ECDSA_method(engine_, &ecdsa_method_, sizeof(ecdsa_method_));
+ }
+
+ int rsa_ex_index() const { return rsa_index_; }
+ int ec_key_ex_index() const { return ec_key_index_; }
+
+ const ENGINE* engine() const { return engine_; }
+
+ static const Keystore2Engine& get() {
+ static Keystore2Engine engine;
+ return engine;
+ }
+
+ private:
+ const int rsa_index_;
+ const int ec_key_index_;
+ RSA_METHOD rsa_method_;
+ ECDSA_METHOD ecdsa_method_;
+ ENGINE* const engine_;
+};
+
+#define OWNERSHIP_TRANSFERRED(x) x.release()
+
+/* wrap_rsa returns an |EVP_PKEY| that contains an RSA key where the public
+ * part is taken from |public_rsa| and the private operations are forwarded to
+ * KeyStore and operate on the key named |key_id|. */
+bssl::UniquePtr<EVP_PKEY> wrap_rsa(std::shared_ptr<Keystore2KeyBackend> key_backend,
+ const RSA* public_rsa) {
+ bssl::UniquePtr<RSA> rsa(RSA_new_method(Keystore2Engine::get().engine()));
+ if (rsa.get() == nullptr) {
+ return nullptr;
+ }
+
+ auto key_backend_copy = new decltype(key_backend)(key_backend);
+
+ if (!RSA_set_ex_data(rsa.get(), Keystore2Engine::get().rsa_ex_index(), key_backend_copy)) {
+ delete key_backend_copy;
+ return nullptr;
+ }
+
+ rsa->n = BN_dup(public_rsa->n);
+ rsa->e = BN_dup(public_rsa->e);
+ if (rsa->n == nullptr || rsa->e == nullptr) {
+ return nullptr;
+ }
+
+ bssl::UniquePtr<EVP_PKEY> result(EVP_PKEY_new());
+ if (result.get() == nullptr || !EVP_PKEY_assign_RSA(result.get(), rsa.get())) {
+ return nullptr;
+ }
+ OWNERSHIP_TRANSFERRED(rsa);
+
+ return result;
+}
+
+/* wrap_ecdsa returns an |EVP_PKEY| that contains an ECDSA key where the public
+ * part is taken from |public_rsa| and the private operations are forwarded to
+ * KeyStore and operate on the key named |key_id|. */
+bssl::UniquePtr<EVP_PKEY> wrap_ecdsa(std::shared_ptr<Keystore2KeyBackend> key_backend,
+ const EC_KEY* public_ecdsa) {
+ bssl::UniquePtr<EC_KEY> ec(EC_KEY_new_method(Keystore2Engine::get().engine()));
+ if (ec.get() == nullptr) {
+ return nullptr;
+ }
+
+ if (!EC_KEY_set_group(ec.get(), EC_KEY_get0_group(public_ecdsa)) ||
+ !EC_KEY_set_public_key(ec.get(), EC_KEY_get0_public_key(public_ecdsa))) {
+ return nullptr;
+ }
+
+ auto key_backend_copy = new decltype(key_backend)(key_backend);
+
+ if (!EC_KEY_set_ex_data(ec.get(), Keystore2Engine::get().ec_key_ex_index(), key_backend_copy)) {
+ delete key_backend_copy;
+ return nullptr;
+ }
+
+ bssl::UniquePtr<EVP_PKEY> result(EVP_PKEY_new());
+ if (result.get() == nullptr || !EVP_PKEY_assign_EC_KEY(result.get(), ec.get())) {
+ return nullptr;
+ }
+ OWNERSHIP_TRANSFERRED(ec);
+
+ return result;
+}
+
+std::optional<std::vector<uint8_t>> keystore2_sign(const Keystore2KeyBackend& key_backend,
+ std::vector<uint8_t> input,
+ KMV1::Algorithm algorithm) {
+ auto sec_level = key_backend.i_keystore_security_level_;
+ ks2::CreateOperationResponse response;
+
+ std::vector<KMV1::KeyParameter> op_params(4);
+ op_params[0] = KMV1::KeyParameter{
+ .tag = KMV1::Tag::PURPOSE,
+ .value = KMV1::KeyParameterValue::make<KMV1::KeyParameterValue::keyPurpose>(
+ KMV1::KeyPurpose::SIGN)};
+ op_params[1] = KMV1::KeyParameter{
+ .tag = KMV1::Tag::ALGORITHM,
+ .value = KMV1::KeyParameterValue::make<KMV1::KeyParameterValue::algorithm>(algorithm)};
+ op_params[2] = KMV1::KeyParameter{
+ .tag = KMV1::Tag::PADDING,
+ .value = KMV1::KeyParameterValue::make<KMV1::KeyParameterValue::paddingMode>(
+ KMV1::PaddingMode::NONE)};
+ op_params[3] =
+ KMV1::KeyParameter{.tag = KMV1::Tag::DIGEST,
+ .value = KMV1::KeyParameterValue::make<KMV1::KeyParameterValue::digest>(
+ KMV1::Digest::NONE)};
+
+ auto rc = sec_level->createOperation(key_backend.descriptor_, op_params, false /* forced */,
+ &response);
+ if (!rc.isOk()) {
+ auto exception_code = rc.getExceptionCode();
+ if (exception_code == EX_SERVICE_SPECIFIC) {
+ LOG(ERROR) << AT << "Keystore createOperation returned service specific error: "
+ << rc.getServiceSpecificError();
+ } else {
+ LOG(ERROR) << AT << "Communication with Keystore createOperation failed error: "
+ << exception_code;
+ }
+ return std::nullopt;
+ }
+
+ auto op = response.iOperation;
+
+ std::optional<std::vector<uint8_t>> output = std::nullopt;
+ rc = op->finish(std::move(input), {}, &output);
+ if (!rc.isOk()) {
+ auto exception_code = rc.getExceptionCode();
+ if (exception_code == EX_SERVICE_SPECIFIC) {
+ LOG(ERROR) << AT << "Keystore finish returned service specific error: "
+ << rc.getServiceSpecificError();
+ } else {
+ LOG(ERROR) << AT
+ << "Communication with Keystore finish failed error: " << exception_code;
+ }
+ return std::nullopt;
+ }
+
+ if (!output) {
+ LOG(ERROR) << AT << "We did not get a signature from Keystore.";
+ }
+
+ return output;
+}
+
+/* rsa_private_transform takes a big-endian integer from |in|, calculates the
+ * d'th power of it, modulo the RSA modulus, and writes the result as a
+ * big-endian integer to |out|. Both |in| and |out| are |len| bytes long. It
+ * returns one on success and zero otherwise. */
+extern "C" int rsa_private_transform(RSA* rsa, uint8_t* out, const uint8_t* in, size_t len) {
+ auto key_backend = reinterpret_cast<std::shared_ptr<Keystore2KeyBackend>*>(
+ RSA_get_ex_data(rsa, Keystore2Engine::get().rsa_ex_index()));
+
+ if (key_backend == nullptr) {
+ LOG(ERROR) << AT << "Invalid key.";
+ return 0;
+ }
+
+ auto output =
+ keystore2_sign(**key_backend, std::vector<uint8_t>(in, in + len), KMV1::Algorithm::RSA);
+ if (!output) {
+ return 0;
+ }
+
+ if (output->size() > len) {
+ /* The result of the RSA operation can never be larger than the size of
+ * the modulus so we assume that the result has extra zeros on the
+ * left. This provides attackers with an oracle, but there's nothing
+ * that we can do about it here. */
+ LOG(WARNING) << "Reply len " << output->size() << " greater than expected " << len;
+ memcpy(out, &output->data()[output->size() - len], len);
+ } else if (output->size() < len) {
+ /* If the Keystore implementation returns a short value we assume that
+ * it's because it removed leading zeros from the left side. This is
+ * bad because it provides attackers with an oracle but we cannot do
+ * anything about a broken Keystore implementation here. */
+ LOG(WARNING) << "Reply len " << output->size() << " less than expected " << len;
+ memset(out, 0, len);
+ memcpy(out + len - output->size(), output->data(), output->size());
+ } else {
+ memcpy(out, output->data(), len);
+ }
+
+ return 1;
+}
+
+/* ecdsa_sign signs |digest_len| bytes from |digest| with |ec_key| and writes
+ * the resulting signature (an ASN.1 encoded blob) to |sig|. It returns one on
+ * success and zero otherwise. */
+extern "C" int ecdsa_sign(const uint8_t* digest, size_t digest_len, uint8_t* sig,
+ unsigned int* sig_len, EC_KEY* ec_key) {
+ auto key_backend = reinterpret_cast<std::shared_ptr<Keystore2KeyBackend>*>(
+ EC_KEY_get_ex_data(ec_key, Keystore2Engine::get().ec_key_ex_index()));
+
+ if (key_backend == nullptr) {
+ LOG(ERROR) << AT << "Invalid key.";
+ return 0;
+ }
+
+ size_t ecdsa_size = ECDSA_size(ec_key);
+
+ auto output = keystore2_sign(**key_backend, std::vector<uint8_t>(digest, digest + digest_len),
+ KMV1::Algorithm::EC);
+ if (!output) {
+ LOG(ERROR) << "There was an error during ecdsa_sign.";
+ return 0;
+ }
+
+ if (output->size() == 0) {
+ LOG(ERROR) << "No valid signature returned";
+ return 0;
+ } else if (output->size() > ecdsa_size) {
+ LOG(ERROR) << "Signature is too large";
+ return 0;
+ }
+
+ memcpy(sig, output->data(), output->size());
+ *sig_len = output->size();
+
+ return 1;
+}
+
+} // namespace
+
+/* EVP_PKEY_from_keystore returns an |EVP_PKEY| that contains either an RSA or
+ * ECDSA key where the public part of the key reflects the value of the key
+ * named |key_id| in Keystore and the private operations are forwarded onto
+ * KeyStore. */
+extern "C" EVP_PKEY* EVP_PKEY_from_keystore2(const char* key_id) {
+ ::ndk::SpAIBinder keystoreBinder(AServiceManager_checkService(keystore2_service_name));
+ auto keystore2 = ks2::IKeystoreService::fromBinder(keystoreBinder);
+
+ if (!keystore2) {
+ LOG(ERROR) << AT << "Unable to connect to Keystore 2.0.";
+ return nullptr;
+ }
+
+ std::string alias = key_id;
+ if (android::base::StartsWith(alias, "USRPKEY_")) {
+ LOG(WARNING) << AT << "Keystore backend used with legacy alias prefix - ignoring.";
+ alias = alias.substr(8);
+ }
+
+ ks2::KeyDescriptor descriptor = {
+ .domain = ks2::Domain::SELINUX,
+ .nspace = getNamespaceforCurrentUid(),
+ .alias = alias,
+ .blob = std::nullopt,
+ };
+
+ // If the key_id starts with the grant id prefix, we parse the following string as numeric
+ // grant id. We can then use the grant domain without alias to load the designated key.
+ if (alias.find(keystore2_grant_id_prefix) == 0) {
+ std::stringstream s(alias.substr(keystore2_grant_id_prefix.size()));
+ s >> std::hex >> reinterpret_cast<uint64_t&>(descriptor.nspace);
+ descriptor.domain = ks2::Domain::GRANT;
+ descriptor.alias = std::nullopt;
+ }
+
+ ks2::KeyEntryResponse response;
+ auto rc = keystore2->getKeyEntry(descriptor, &response);
+ if (!rc.isOk()) {
+ auto exception_code = rc.getExceptionCode();
+ if (exception_code == EX_SERVICE_SPECIFIC) {
+ LOG(ERROR) << AT << "Keystore getKeyEntry returned service specific error: "
+ << rc.getServiceSpecificError();
+ } else {
+ LOG(ERROR) << AT << "Communication with Keystore getKeyEntry failed error: "
+ << exception_code;
+ }
+ return nullptr;
+ }
+
+ if (!response.metadata.certificate) {
+ LOG(ERROR) << AT << "No public key found.";
+ return nullptr;
+ }
+
+ const uint8_t* p = response.metadata.certificate->data();
+ bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &p, response.metadata.certificate->size()));
+ if (!x509) {
+ LOG(ERROR) << AT << "Failed to parse x509 certificate.";
+ return nullptr;
+ }
+ bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
+ if (!pkey) {
+ LOG(ERROR) << AT << "Failed to extract public key.";
+ return nullptr;
+ }
+
+ auto key_backend = std::make_shared<Keystore2KeyBackend>(
+ Keystore2KeyBackend{response.metadata.key, response.iSecurityLevel});
+
+ bssl::UniquePtr<EVP_PKEY> result;
+ switch (EVP_PKEY_type(pkey->type)) {
+ case EVP_PKEY_RSA: {
+ bssl::UniquePtr<RSA> public_rsa(EVP_PKEY_get1_RSA(pkey.get()));
+ result = wrap_rsa(key_backend, public_rsa.get());
+ break;
+ }
+ case EVP_PKEY_EC: {
+ bssl::UniquePtr<EC_KEY> public_ecdsa(EVP_PKEY_get1_EC_KEY(pkey.get()));
+ result = wrap_ecdsa(key_backend, public_ecdsa.get());
+ break;
+ }
+ default:
+ LOG(ERROR) << AT << "Unsupported key type " << EVP_PKEY_type(pkey->type);
+ return nullptr;
+ }
+
+ return result.release();
+}
diff --git a/keystore-engine/keystore2_engine.h b/keystore-engine/keystore2_engine.h
new file mode 100644
index 0000000..a8381d9
--- /dev/null
+++ b/keystore-engine/keystore2_engine.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <openssl/evp.h>
+
+extern "C" EVP_PKEY* EVP_PKEY_from_keystore2(const char* key_id);
diff --git a/keystore/Android.bp b/keystore/Android.bp
index 45b721b..7278cee 100644
--- a/keystore/Android.bp
+++ b/keystore/Android.bp
@@ -1,3 +1,13 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_defaults {
name: "keystore_defaults",
diff --git a/keystore/auth_token_table.cpp b/keystore/auth_token_table.cpp
index 5e6d572..971f9ef 100644
--- a/keystore/auth_token_table.cpp
+++ b/keystore/auth_token_table.cpp
@@ -178,33 +178,39 @@
int64_t authTokenMaxAgeMillis) {
std::vector<uint64_t> sids = {secureUserId};
HardwareAuthenticatorType auth_type = HardwareAuthenticatorType::ANY;
-
time_t now = clock_function_();
+ int64_t nowMillis = now * 1000;
- // challenge-based - the authToken has to contain the given challenge.
- if (challenge != 0) {
- auto matching_op = find_if(
- entries_, [&](Entry& e) { return e.token().challenge == challenge && !e.completed(); });
- if (matching_op == entries_.end()) {
- return {AUTH_TOKEN_NOT_FOUND, {}};
- }
-
- if (!matching_op->SatisfiesAuth(sids, auth_type)) {
- return {AUTH_TOKEN_WRONG_SID, {}};
- }
-
- if (authTokenMaxAgeMillis > 0) {
- if (static_cast<int64_t>(matching_op->time_received()) + authTokenMaxAgeMillis <
- static_cast<int64_t>(now)) {
- return {AUTH_TOKEN_EXPIRED, {}};
- }
- }
-
- return {OK, matching_op->token()};
+ // It's an error to call this without a non-zero challenge.
+ if (challenge == 0) {
+ return {OP_HANDLE_REQUIRED, {}};
}
- // Otherwise, no challenge - any authToken younger than the specified maximum
- // age will do.
+ // First see if we can find a token which matches the given challenge. If we
+ // can, return the newest one. We specifically don't care about its age.
+ //
+ Entry* newest_match_for_challenge = nullptr;
+ for (auto& entry : entries_) {
+ if (entry.token().challenge == challenge && !entry.completed() &&
+ entry.SatisfiesAuth(sids, auth_type)) {
+ if (newest_match_for_challenge == nullptr ||
+ entry.is_newer_than(newest_match_for_challenge)) {
+ newest_match_for_challenge = &entry;
+ }
+ }
+ }
+ if (newest_match_for_challenge != nullptr) {
+ newest_match_for_challenge->UpdateLastUse(now);
+ return {OK, newest_match_for_challenge->token()};
+ }
+
+ // If that didn't work, we'll take the most recent token within the specified
+ // deadline, if any. Of course if the deadline is zero it doesn't make sense
+ // to look at all.
+ if (authTokenMaxAgeMillis == 0) {
+ return {AUTH_TOKEN_NOT_FOUND, {}};
+ }
+
Entry* newest_match = nullptr;
for (auto& entry : entries_) {
if (entry.SatisfiesAuth(sids, auth_type) && entry.is_newer_than(newest_match)) {
@@ -216,11 +222,9 @@
return {AUTH_TOKEN_NOT_FOUND, {}};
}
- if (authTokenMaxAgeMillis > 0) {
- if (static_cast<int64_t>(newest_match->time_received()) + authTokenMaxAgeMillis <
- static_cast<int64_t>(now)) {
- return {AUTH_TOKEN_EXPIRED, {}};
- }
+ int64_t tokenAgeMillis = nowMillis - newest_match->time_received() * 1000;
+ if (tokenAgeMillis >= authTokenMaxAgeMillis) {
+ return {AUTH_TOKEN_EXPIRED, {}};
}
newest_match->UpdateLastUse(now);
diff --git a/keystore/binder/android/security/keystore/IKeystoreService.aidl b/keystore/binder/android/security/keystore/IKeystoreService.aidl
index e0879dd..3b9a1b4 100644
--- a/keystore/binder/android/security/keystore/IKeystoreService.aidl
+++ b/keystore/binder/android/security/keystore/IKeystoreService.aidl
@@ -87,7 +87,20 @@
int onKeyguardVisibilityChanged(in boolean isShowing, in int userId);
int listUidsOfAuthBoundKeys(out @utf8InCpp List<String> uids);
- // Called by credstore (and only credstore).
+ // This method looks through auth-tokens cached by keystore which match
+ // the passed-in |secureUserId|.
+ //
+ // If one or more of these tokens has a |challenge| field which matches
+ // the passed-in |challenge| parameter, the most recent is returned. In
+ // this case the |authTokenMaxAgeMillis| parameter is not used.
+ //
+ // Otherwise, the most recent auth-token of these tokens which is younger
+ // than |authTokenMaxAgeMillis| is returned.
+ //
+ // The passed in |challenge| parameter must always be non-zero.
+ //
+ // This method is called by credstore (and only credstore).
+ //
void getTokensForCredstore(in long challenge, in long secureUserId, in int authTokenMaxAgeMillis,
in ICredstoreTokenCallback cb);
}
diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp
index 883e020..327eb93 100644
--- a/keystore/tests/Android.bp
+++ b/keystore/tests/Android.bp
@@ -1,5 +1,14 @@
// Unit test for AuthTokenTable
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_test {
cflags: [
"-Wall",
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 0a5fb29..ee71db3 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -12,18 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-rust_library {
- name: "libkeystore2",
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+ name: "libkeystore2_defaults",
crate_name: "keystore2",
srcs: ["src/lib.rs"],
rustlibs: [
"android.hardware.security.keymint-V1-rust",
"android.hardware.security.secureclock-V1-rust",
+ "android.os.permissions_aidl-rust",
"android.security.apc-rust",
"android.security.authorization-rust",
"android.security.compat-rust",
"android.security.remoteprovisioning-rust",
+ "android.security.usermanager-rust",
"android.system.keystore2-V1-rust",
"libanyhow",
"libbinder_rs",
@@ -32,6 +43,8 @@
"libkeystore2_crypto_rust",
"libkeystore2_km_compat",
"libkeystore2_selinux",
+ "libkeystore2_system_property-rust",
+ "libkeystore2_vintf_rust",
"liblazy_static",
"liblibc",
"liblibsqlite3_sys",
@@ -43,6 +56,11 @@
}
rust_library {
+ name: "libkeystore2",
+ defaults: ["libkeystore2_defaults"],
+}
+
+rust_library {
name: "libkeystore2_test_utils",
crate_name: "keystore2_test_utils",
srcs: ["test_utils/lib.rs"],
@@ -55,34 +73,13 @@
rust_test {
name: "keystore2_test",
crate_name: "keystore2",
- srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
compile_multilib: "first",
+ defaults: ["libkeystore2_defaults"],
rustlibs: [
- "android.hardware.security.keymint-V1-rust",
- "android.hardware.security.secureclock-V1-rust",
- "android.security.apc-rust",
- "android.security.authorization-rust",
- "android.security.compat-rust",
- "android.security.remoteprovisioning-rust",
- "android.system.keystore2-V1-rust",
"libandroid_logger",
- "libanyhow",
- "libbinder_rs",
- "libkeystore2_aaid-rust",
- "libkeystore2_apc_compat-rust",
- "libkeystore2_crypto_rust",
- "libkeystore2_km_compat",
- "libkeystore2_selinux",
"libkeystore2_test_utils",
- "liblazy_static",
- "liblibc",
- "liblibsqlite3_sys",
- "liblog_rust",
- "librand",
- "librusqlite",
- "libthiserror",
],
}
@@ -94,6 +91,7 @@
"libbinder_rs",
"libkeystore2",
"liblog_rust",
+ "libvpnprofilestore-rust",
],
init_rc: ["keystore2.rc"],
}
diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING
index 33d157e..99a1e60 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -1,10 +1,10 @@
{
"presubmit": [
{
- "name": "keystore2_certificate_test"
+ "name": "keystore2_crypto_test"
},
{
- "name": "keystore2_km_compat_test"
+ "name": "keystore2_crypto_test_rust"
},
{
"name": "keystore2_test"
diff --git a/keystore2/aaid/Android.bp b/keystore2/aaid/Android.bp
index 2329400..d27fdf6 100644
--- a/keystore2/aaid/Android.bp
+++ b/keystore2/aaid/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_library {
name: "libkeystore2_aaid",
srcs: [
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index 36cff16..f30ad83 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
aidl_interface {
name: "android.security.attestationmanager",
srcs: [ "android/security/attestationmanager/*.aidl", ],
@@ -105,3 +114,38 @@
},
},
}
+
+aidl_interface {
+ name: "android.security.usermanager",
+ srcs: [ "android/security/usermanager/*.aidl" ],
+ imports: [
+ "android.system.keystore2",
+ ],
+ unstable: true,
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ rust: {
+ enabled: true,
+ },
+ ndk: {
+ enabled: true,
+ }
+ },
+}
+
+aidl_interface {
+ name: "android.security.vpnprofilestore",
+ srcs: [ "android/security/vpnprofilestore/*.aidl" ],
+ unstable: true,
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
index d045345..0d4c30f 100644
--- a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
+++ b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
@@ -16,6 +16,7 @@
package android.security.remoteprovisioning;
+import android.hardware.security.keymint.ProtectedData;
import android.hardware.security.keymint.SecurityLevel;
import android.security.remoteprovisioning.AttestationPoolStatus;
@@ -83,10 +84,13 @@
* @param secLevel The security level to specify which KM instance from which to generate a
* CSR.
*
- * @return A CBOR blob composed of various encrypted/signed elements from the TA in a byte[]
+ * @param protectedData The encrypted CBOR blob generated by the remote provisioner
+ *
+ * @return A CBOR blob composed of various elements required by the server to verify the
+ * request.
*/
byte[] generateCsr(in boolean testMode, in int numCsr, in byte[] eek, in byte[] challenge,
- in SecurityLevel secLevel);
+ in SecurityLevel secLevel, out ProtectedData protectedData);
/**
* This method provides a way for the returned attestation certificate chains to be provisioned
@@ -95,7 +99,10 @@
*
* @param publicKey The raw public key encoded in the leaf certificate.
*
- * @param cert An X.509, DER encoded certificate chain.
+ * @param batchCert The batch certificate corresponding to the attestation key. Separated for
+ * the purpose of making Subject lookup for KM attestation easier.
+ *
+ * @param certs An X.509, DER encoded certificate chain for the attestation key.
*
* @param expirationDate The expiration date on the certificate chain, provided by the caller
* for convenience.
@@ -103,8 +110,8 @@
* @param secLevel The security level representing the KM instance containing the key that this
* chain corresponds to.
*/
- void provisionCertChain(in byte[] publicKey, in byte[] certs, in long expirationDate,
- in SecurityLevel secLevel);
+ void provisionCertChain(in byte[] publicKey, in byte[] batchCert, in byte[] certs,
+ in long expirationDate, in SecurityLevel secLevel);
/**
* This method allows the caller to instruct KeyStore to generate and store a key pair to be
@@ -117,4 +124,13 @@
* @param secLevel The security level to specify which KM instance should generate a key pair.
*/
void generateKeyPair(in boolean is_test_mode, in SecurityLevel secLevel);
+
+ /**
+ * This method returns the SecurityLevels of whichever instances of
+ * IRemotelyProvisionedComponent are running on the device. The RemoteProvisioner app needs to
+ * know which KM instances it should be generating and managing attestation keys for.
+ *
+ * @return The array of security levels.
+ */
+ SecurityLevel[] getSecurityLevels();
}
diff --git a/keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl b/keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl
new file mode 100644
index 0000000..83edb1a
--- /dev/null
+++ b/keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl
@@ -0,0 +1,78 @@
+// Copyright 2021, 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.
+
+package android.security.usermanager;
+
+import android.system.keystore2.Domain;
+
+// TODO: mark the interface with @SensitiveData when the annotation is ready (b/176110256).
+
+/**
+ * IKeystoreUserManager interface exposes the methods for adding/removing users and changing the
+ * user's password.
+ * @hide
+ */
+interface IKeystoreUserManager {
+
+ /**
+ * Allows LockSettingsService to inform keystore about adding a new user.
+ * Callers require 'AddUser' permission.
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'AddUser' permission.
+ * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of an existing user with the same
+ * user id.
+ *
+ * @param userId - Android user id
+ * @hide
+ */
+ void onUserAdded(in int userId);
+
+ /**
+ * Allows LockSettingsService to inform keystore about removing a user.
+ * Callers require 'RemoveUser' permission.
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'RemoveUser' permission.
+ * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of the user being deleted.
+ *
+ * @param userId - Android user id
+ * @hide
+ */
+ void onUserRemoved(in int userId);
+
+ /**
+ * Allows LockSettingsService to inform keystore about password change of a user.
+ * Callers require 'ChangePassword' permission.
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'ChangePassword'
+ * permission.
+ * `ResponseCode::SYSTEM_ERROR` - if failed to delete the super encrypted keys of the user.
+ * `ResponseCode::Locked' - if the keystore is locked for the given user.
+ *
+ * @param userId - Android user id
+ * @param password - a secret derived from the synthetic password of the user
+ * @hide
+ */
+ void onUserPasswordChanged(in int userId, in @nullable byte[] password);
+
+ /**
+ * This function deletes all keys within a namespace. It mainly gets called when an app gets
+ * removed and all resources of this app need to be cleaned up.
+ *
+ * @param domain - One of Domain.APP or Domain.SELINUX.
+ * @param nspace - The UID of the app that is to be cleared if domain is Domain.APP or
+ * the SEPolicy namespace if domain is Domain.SELINUX.
+ * @hide
+ */
+ void clearNamespace(Domain domain, long nspace);
+}
diff --git a/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl b/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl
new file mode 100644
index 0000000..054a4d7
--- /dev/null
+++ b/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package android.security.vpnprofilestore;
+
+/**
+ * Internal interface for accessing and storing VPN profiles.
+ *
+ * @hide
+ */
+interface IVpnProfileStore {
+ /**
+ * Service specific error code indicating that the profile was not found.
+ */
+ const int ERROR_PROFILE_NOT_FOUND = 1;
+
+ /**
+ * Service specific error code indicating that an unexpected system error occurred.
+ */
+ const int ERROR_SYSTEM_ERROR = 2;
+
+ /**
+ * Returns the profile stored under the given alias.
+ *
+ * @param alias name of the profile.
+ * @return The unstructured blob that was passed as profile parameter into put()
+ */
+ byte[] get(in String alias);
+
+ /**
+ * Stores one profile as unstructured blob under the given alias.
+ */
+ void put(in String alias, in byte[] profile);
+
+ /**
+ * Deletes the profile under the given alias.
+ */
+ void remove(in String alias);
+
+ /**
+ * Returns a list of aliases of profiles stored. The list is filtered by prefix.
+ * The resulting strings are the full aliases including the prefix.
+ */
+ String[] list(in String prefix);
+}
\ No newline at end of file
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
index 405e9b8..9519c8e 100644
--- a/keystore2/apc_compat/Android.bp
+++ b/keystore2/apc_compat/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
cc_library {
name: "libkeystore2_apc_compat",
srcs: [
diff --git a/keystore2/keystore2.rc b/keystore2/keystore2.rc
index c5fc72a..2d1f05a 100644
--- a/keystore2/keystore2.rc
+++ b/keystore2/keystore2.rc
@@ -8,13 +8,12 @@
# Start Keystore 2 conditionally
# TODO b/171563717 Remove when Keystore 2 migration is complete.
-on nonencrypted && property:persist.android.security.keystore2.enable=true
+on property:persist.android.security.keystore2.enable=true
enable keystore2
service keystore2 /system/bin/keystore2 /data/misc/keystore
- class main
+ class early_hal
user keystore
group keystore readproc log
writepid /dev/cpuset/foreground/tasks
- # TODO b/171563717 Remove when Keystore 2 migration is complete.
disabled
diff --git a/keystore2/selinux/Android.bp b/keystore2/selinux/Android.bp
index acbf5ef..18063d3 100644
--- a/keystore2/selinux/Android.bp
+++ b/keystore2/selinux/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
rust_library {
name: "libkeystore2_selinux",
crate_name: "keystore2_selinux",
diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs
index 6edd760..2b36f1f 100644
--- a/keystore2/src/async_task.rs
+++ b/keystore2/src/async_task.rs
@@ -19,9 +19,9 @@
//! processed all tasks before it terminates.
//! Note that low priority tasks are processed only when the high priority queue is empty.
-use std::time::Duration;
+use std::{any::Any, any::TypeId, time::Duration};
use std::{
- collections::VecDeque,
+ collections::{HashMap, VecDeque},
sync::Arc,
sync::{Condvar, Mutex, MutexGuard},
thread,
@@ -33,15 +33,74 @@
Running,
}
+/// The Shelf allows async tasks to store state across invocations.
+/// Note: Store elves at your own peril ;-).
+#[derive(Debug, Default)]
+pub struct Shelf(HashMap<TypeId, Box<dyn Any + Send>>);
+
+impl Shelf {
+ /// Get a reference to the shelved data of type T. Returns Some if the data exists.
+ pub fn get_downcast_ref<T: Any + Send>(&self) -> Option<&T> {
+ self.0.get(&TypeId::of::<T>()).and_then(|v| v.downcast_ref::<T>())
+ }
+
+ /// Get a mutable reference to the shelved data of type T. If a T was inserted using put,
+ /// get_mut, or get_or_put_with.
+ pub fn get_downcast_mut<T: Any + Send>(&mut self) -> Option<&mut T> {
+ self.0.get_mut(&TypeId::of::<T>()).and_then(|v| v.downcast_mut::<T>())
+ }
+
+ /// Remove the entry of the given type and returns the stored data if it existed.
+ pub fn remove_downcast_ref<T: Any + Send>(&mut self) -> Option<T> {
+ self.0.remove(&TypeId::of::<T>()).and_then(|v| v.downcast::<T>().ok().map(|b| *b))
+ }
+
+ /// Puts data `v` on the shelf. If there already was an entry of type T it is returned.
+ pub fn put<T: Any + Send>(&mut self, v: T) -> Option<T> {
+ self.0
+ .insert(TypeId::of::<T>(), Box::new(v) as Box<dyn Any + Send>)
+ .and_then(|v| v.downcast::<T>().ok().map(|b| *b))
+ }
+
+ /// Gets a mutable reference to the entry of the given type and default creates it if necessary.
+ /// The type must implement Default.
+ pub fn get_mut<T: Any + Send + Default>(&mut self) -> &mut T {
+ self.0
+ .entry(TypeId::of::<T>())
+ .or_insert_with(|| Box::new(T::default()) as Box<dyn Any + Send>)
+ .downcast_mut::<T>()
+ .unwrap()
+ }
+
+ /// Gets a mutable reference to the entry of the given type or creates it using the init
+ /// function. Init is not executed if the entry already existed.
+ pub fn get_or_put_with<T: Any + Send, F>(&mut self, init: F) -> &mut T
+ where
+ F: FnOnce() -> T,
+ {
+ self.0
+ .entry(TypeId::of::<T>())
+ .or_insert_with(|| Box::new(init()) as Box<dyn Any + Send>)
+ .downcast_mut::<T>()
+ .unwrap()
+ }
+}
+
struct AsyncTaskState {
state: State,
thread: Option<thread::JoinHandle<()>>,
- hi_prio_req: VecDeque<Box<dyn FnOnce() + Send>>,
- lo_prio_req: VecDeque<Box<dyn FnOnce() + Send>>,
+ hi_prio_req: VecDeque<Box<dyn FnOnce(&mut Shelf) + Send>>,
+ lo_prio_req: VecDeque<Box<dyn FnOnce(&mut Shelf) + Send>>,
+ /// The store allows tasks to store state across invocations. It is passed to each invocation
+ /// of each task. Tasks need to cooperate on the ids they use for storing state.
+ shelf: Option<Shelf>,
}
/// AsyncTask spawns one worker thread on demand to process jobs inserted into
-/// a low and a high priority work queue.
+/// a low and a high priority work queue. The queues are processed FIFO, and low
+/// priority queue is processed if the high priority queue is empty.
+/// Note: Because there is only one worker thread at a time for a given AsyncTask instance,
+/// all scheduled requests are guaranteed to be serialized with respect to one another.
pub struct AsyncTask {
state: Arc<(Condvar, Mutex<AsyncTaskState>)>,
}
@@ -56,6 +115,7 @@
thread: None,
hi_prio_req: VecDeque::new(),
lo_prio_req: VecDeque::new(),
+ shelf: None,
}),
)),
}
@@ -68,7 +128,7 @@
/// preempt them.
pub fn queue_hi<F>(&self, f: F)
where
- F: FnOnce() + Send + 'static,
+ F: for<'r> FnOnce(&'r mut Shelf) + Send + 'static,
{
self.queue(f, true)
}
@@ -79,14 +139,14 @@
/// priority jobs.
pub fn queue_lo<F>(&self, f: F)
where
- F: FnOnce() + Send + 'static,
+ F: FnOnce(&mut Shelf) + Send + 'static,
{
self.queue(f, false)
}
fn queue<F>(&self, f: F, hi_prio: bool)
where
- F: FnOnce() + Send + 'static,
+ F: for<'r> FnOnce(&'r mut Shelf) + Send + 'static,
{
let (ref condvar, ref state) = *self.state;
let mut state = state.lock().unwrap();
@@ -112,6 +172,8 @@
state.thread = Some(thread::spawn(move || {
let (ref condvar, ref state) = *cloned_state;
+ // When the worker starts, it takes the shelf and puts it on the stack.
+ let mut shelf = state.lock().unwrap().shelf.take().unwrap_or_default();
loop {
if let Some(f) = {
let (mut state, timeout) = condvar
@@ -129,16 +191,132 @@
(Some(f), _, _) => Some(f),
(None, false, _) => state.lo_prio_req.pop_front(),
(None, true, true) => {
+ // When the worker exits it puts the shelf back into the shared
+ // state for the next worker to use. So state is preserved not
+ // only across invocations but also across worker thread shut down.
+ state.shelf = Some(shelf);
state.state = State::Exiting;
break;
}
(None, true, false) => None,
}
} {
- f()
+ f(&mut shelf)
}
}
}));
state.state = State::Running;
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{AsyncTask, Shelf};
+ use std::sync::mpsc::channel;
+
+ #[test]
+ fn test_shelf() {
+ let mut shelf = Shelf::default();
+
+ let s = "A string".to_string();
+ assert_eq!(shelf.put(s), None);
+
+ let s2 = "Another string".to_string();
+ assert_eq!(shelf.put(s2), Some("A string".to_string()));
+
+ // Put something of a different type on the shelf.
+ #[derive(Debug, PartialEq, Eq)]
+ struct Elf {
+ pub name: String,
+ }
+ let e1 = Elf { name: "Glorfindel".to_string() };
+ assert_eq!(shelf.put(e1), None);
+
+ // The String value is still on the shelf.
+ let s3 = shelf.get_downcast_ref::<String>().unwrap();
+ assert_eq!(s3, "Another string");
+
+ // As is the Elf.
+ {
+ let e2 = shelf.get_downcast_mut::<Elf>().unwrap();
+ assert_eq!(e2.name, "Glorfindel");
+ e2.name = "Celeborn".to_string();
+ }
+
+ // Take the Elf off the shelf.
+ let e3 = shelf.remove_downcast_ref::<Elf>().unwrap();
+ assert_eq!(e3.name, "Celeborn");
+
+ assert_eq!(shelf.remove_downcast_ref::<Elf>(), None);
+
+ // No u64 value has been put on the shelf, so getting one gives the default value.
+ {
+ let i = shelf.get_mut::<u64>();
+ assert_eq!(*i, 0);
+ *i = 42;
+ }
+ let i2 = shelf.get_downcast_ref::<u64>().unwrap();
+ assert_eq!(*i2, 42);
+
+ // No i32 value has ever been seen near the shelf.
+ assert_eq!(shelf.get_downcast_ref::<i32>(), None);
+ assert_eq!(shelf.get_downcast_mut::<i32>(), None);
+ assert_eq!(shelf.remove_downcast_ref::<i32>(), None);
+ }
+
+ #[test]
+ fn test_async_task() {
+ let at = AsyncTask::default();
+
+ // First queue up a job that blocks until we release it, to avoid
+ // unpredictable synchronization.
+ let (start_sender, start_receiver) = channel();
+ at.queue_hi(move |shelf| {
+ start_receiver.recv().unwrap();
+ // Put a trace vector on the shelf
+ shelf.put(Vec::<String>::new());
+ });
+
+ // Queue up some high-priority and low-priority jobs.
+ for i in 0..3 {
+ let j = i;
+ at.queue_lo(move |shelf| {
+ let trace = shelf.get_mut::<Vec<String>>();
+ trace.push(format!("L{}", j));
+ });
+ let j = i;
+ at.queue_hi(move |shelf| {
+ let trace = shelf.get_mut::<Vec<String>>();
+ trace.push(format!("H{}", j));
+ });
+ }
+
+ // Finally queue up a low priority job that emits the trace.
+ let (trace_sender, trace_receiver) = channel();
+ at.queue_lo(move |shelf| {
+ let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap();
+ trace_sender.send(trace.clone()).unwrap();
+ });
+
+ // Ready, set, go.
+ start_sender.send(()).unwrap();
+ let trace = trace_receiver.recv().unwrap();
+
+ assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_async_task_panic() {
+ let at = AsyncTask::default();
+ at.queue_hi(|_shelf| {
+ panic!("Panic from queued job");
+ });
+ // Queue another job afterwards to ensure that the async thread gets joined.
+ let (done_sender, done_receiver) = channel();
+ at.queue_hi(move |_shelf| {
+ done_sender.send(()).unwrap();
+ });
+ done_receiver.recv().unwrap();
+ }
+}
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 40a0c80..02b19c4 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module implements IKeyAuthorization AIDL interface.
+//! This module implements IKeystoreAuthorization AIDL interface.
use crate::error::Error as KeystoreError;
use crate::error::map_or_log_err;
-use crate::globals::{DB, ENFORCEMENTS, LEGACY_BLOB_LOADER, SUPER_KEY};
+use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
use crate::permission::KeystorePerm;
+use crate::super_key::UserState;
use crate::utils::check_keystore_permission;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
@@ -65,22 +66,22 @@
.context("In on_lock_screen_event: Unlock with password.")?;
ENFORCEMENTS.set_device_locked(user_id, false);
// Unlock super key.
- DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- //TODO - b/176123105 - Once the user management API is implemented, unlock is
- //allowed only if the user is added. Then the two tasks handled by the
- //unlock_user_key will be split into two methods. For now, unlock_user_key
- //method is used as it is, which created a super key for the user if one does
- //not exists, in addition to unlocking the existing super key of the user/
- SUPER_KEY.unlock_user_key(
- user_id as u32,
- user_password,
- &mut db,
- &LEGACY_BLOB_LOADER,
- )?;
- Ok(())
- })
- .context("In on_lock_screen_event.")?;
+ if let UserState::Uninitialized = DB
+ .with(|db| {
+ UserState::get_with_password_unlock(
+ &mut db.borrow_mut(),
+ &LEGACY_MIGRATOR,
+ &SUPER_KEY,
+ user_id as u32,
+ user_password,
+ )
+ })
+ .context("In on_lock_screen_event: Unlock with password.")?
+ {
+ log::info!(
+ "In on_lock_screen_event. Trying to unlock when LSKF is uninitialized."
+ );
+ }
Ok(())
}
diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp
index 9ecd823..e386735 100644
--- a/keystore2/src/crypto/Android.bp
+++ b/keystore2/src/crypto/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
rust_library {
name: "libkeystore2_crypto_rust",
crate_name: "keystore2_crypto",
@@ -65,6 +74,7 @@
"--whitelist-function", "ECPOINTOct2Point",
"--whitelist-function", "EC_KEY_free",
"--whitelist-function", "EC_POINT_free",
+ "--whitelist-function", "extractSubjectFromCertificate",
"--whitelist-type", "EC_KEY",
"--whitelist-type", "EC_POINT",
"--whitelist-var", "EC_MAX_BYTES",
diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp
index 3cc19c5..2e613fd 100644
--- a/keystore2/src/crypto/crypto.cpp
+++ b/keystore2/src/crypto/crypto.cpp
@@ -26,6 +26,7 @@
#include <openssl/evp.h>
#include <openssl/hkdf.h>
#include <openssl/rand.h>
+#include <openssl/x509.h>
#include <vector>
@@ -261,3 +262,42 @@
}
return point;
}
+
+int extractSubjectFromCertificate(const uint8_t* cert_buf, size_t cert_len, uint8_t* subject_buf,
+ size_t subject_buf_len) {
+ if (!cert_buf || !subject_buf) {
+ ALOGE("extractSubjectFromCertificate: received null pointer");
+ return 0;
+ }
+
+ const uint8_t* p = cert_buf;
+ bssl::UniquePtr<X509> cert(d2i_X509(nullptr /* Allocate X509 struct */, &p, cert_len));
+ if (!cert) {
+ ALOGE("extractSubjectFromCertificate: failed to parse certificate");
+ return 0;
+ }
+
+ X509_NAME* subject = X509_get_subject_name(cert.get());
+ if (!subject) {
+ ALOGE("extractSubjectFromCertificate: failed to retrieve subject name");
+ return 0;
+ }
+
+ int subject_len = i2d_X509_NAME(subject, nullptr /* Don't copy the data */);
+ if (subject_len < 0) {
+ ALOGE("extractSubjectFromCertificate: error obtaining encoded subject name length");
+ return 0;
+ }
+
+ if (subject_len > subject_buf_len) {
+ // Return the subject length, negated, so the caller knows how much
+ // buffer space is required.
+ ALOGI("extractSubjectFromCertificate: needed %d bytes for subject, caller provided %zu",
+ subject_len, subject_buf_len);
+ return -subject_len;
+ }
+
+ // subject_buf has enough space.
+ uint8_t* tmp = subject_buf;
+ return i2d_X509_NAME(subject, &tmp);
+}
diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp
index 9bd7758..6686c8c 100644
--- a/keystore2/src/crypto/crypto.hpp
+++ b/keystore2/src/crypto/crypto.hpp
@@ -60,6 +60,29 @@
size_t ECPOINTPoint2Oct(const EC_POINT *point, uint8_t *buf, size_t len);
EC_POINT* ECPOINTOct2Point(const uint8_t *buf, size_t len);
+
}
+// Parse a DER-encoded X.509 certificate contained in cert_buf, with length
+// cert_len, extract the subject, DER-encode it and write the result to
+// subject_buf, which has subject_buf_len capacity.
+//
+// Because the length of the subject is unknown, and because we'd like to (a) be
+// able to handle subjects of any size and (b) avoid parsing the certificate
+// twice most of the time, once to discover the length and once to parse it, the
+// return value is overloaded.
+//
+// If the return value > 0 it specifies the number of bytes written into
+// subject_buf; the operation was successful.
+//
+// If the return value == 0, certificate parsing failed unrecoverably. The
+// reason will be logged.
+//
+// If the return value < 0, the operation failed because the subject size >
+// subject_buf_len. The return value is -(subject_size), where subject_size is
+// the size of the extracted DER-encoded subject field. Call
+// extractSubjectFromCertificate again with a sufficiently-large buffer.
+int extractSubjectFromCertificate(const uint8_t* cert_buf, size_t cert_len,
+ uint8_t* subject_buf, size_t subject_buf_len);
+
#endif // __CRYPTO_H__
diff --git a/keystore2/src/crypto/error.rs b/keystore2/src/crypto/error.rs
index 1e84fc6..1eec321 100644
--- a/keystore2/src/crypto/error.rs
+++ b/keystore2/src/crypto/error.rs
@@ -85,4 +85,8 @@
/// This is returned if the C implementation of ECPOINTOct2Point returned null.
#[error("Failed to convert oct to point.")]
ECOct2PointFailed,
+
+ /// This is returned if the C implementation of extractSubjectFromCertificate failed.
+ #[error("Failed to extract certificate subject.")]
+ ExtractSubjectFailed,
}
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 92b257c..77dab67 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -19,11 +19,12 @@
mod zvec;
pub use error::Error;
use keystore2_crypto_bindgen::{
- generateKeyFromPassword, randomBytes, AES_gcm_decrypt, AES_gcm_encrypt, ECDHComputeKey,
- ECKEYDeriveFromSecret, ECKEYGenerateKey, ECPOINTOct2Point, ECPOINTPoint2Oct, EC_KEY_free,
- EC_KEY_get0_public_key, EC_POINT_free, HKDFExpand, HKDFExtract, EC_KEY, EC_MAX_BYTES, EC_POINT,
- EVP_MAX_MD_SIZE,
+ extractSubjectFromCertificate, generateKeyFromPassword, randomBytes, AES_gcm_decrypt,
+ AES_gcm_encrypt, ECDHComputeKey, ECKEYDeriveFromSecret, ECKEYGenerateKey, ECPOINTOct2Point,
+ ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key, EC_POINT_free, HKDFExpand, HKDFExtract,
+ EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE,
};
+use std::convert::TryFrom;
use std::convert::TryInto;
use std::marker::PhantomData;
pub use zvec::ZVec;
@@ -353,6 +354,54 @@
Ok(OwnedECPoint(result))
}
+/// Uses BoringSSL to extract the DER-encoded subject from a DER-encoded X.509 certificate.
+pub fn parse_subject_from_certificate(cert_buf: &[u8]) -> Result<Vec<u8>, Error> {
+ // Try with a 200-byte output buffer, should be enough in all but bizarre cases.
+ let mut retval = vec![0; 200];
+
+ // Safety: extractSubjectFromCertificate reads at most cert_buf.len() bytes from cert_buf and
+ // writes at most retval.len() bytes to retval.
+ let mut size = unsafe {
+ extractSubjectFromCertificate(
+ cert_buf.as_ptr(),
+ cert_buf.len(),
+ retval.as_mut_ptr(),
+ retval.len(),
+ )
+ };
+
+ if size == 0 {
+ return Err(Error::ExtractSubjectFailed);
+ }
+
+ if size < 0 {
+ // Our buffer wasn't big enough. Make one that is just the right size and try again.
+ let negated_size = usize::try_from(-size).map_err(|_e| Error::ExtractSubjectFailed)?;
+ retval = vec![0; negated_size];
+
+ // Safety: extractSubjectFromCertificate reads at most cert_buf.len() bytes from cert_buf
+ // and writes at most retval.len() bytes to retval.
+ size = unsafe {
+ extractSubjectFromCertificate(
+ cert_buf.as_ptr(),
+ cert_buf.len(),
+ retval.as_mut_ptr(),
+ retval.len(),
+ )
+ };
+
+ if size <= 0 {
+ return Err(Error::ExtractSubjectFailed);
+ }
+ }
+
+ // Reduce buffer size to the amount written.
+ let safe_size = usize::try_from(size).map_err(|_e| Error::ExtractSubjectFailed)?;
+ retval.truncate(safe_size);
+
+ Ok(retval)
+}
+
#[cfg(test)]
mod tests {
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 3789d28..57ca7aa 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -41,12 +41,15 @@
//! from the database module these functions take permission check
//! callbacks.
-use crate::db_utils::{self, SqlField};
use crate::error::{Error as KsError, ErrorCode, ResponseCode};
use crate::impl_metadata; // This is in db_utils.rs
use crate::key_parameter::{KeyParameter, Tag};
use crate::permission::KeyPermSet;
-use crate::utils::get_current_time_in_seconds;
+use crate::utils::{get_current_time_in_seconds, AID_USER_OFFSET};
+use crate::{
+ db_utils::{self, SqlField},
+ gc::Gc,
+};
use anyhow::{anyhow, Context, Result};
use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError};
@@ -95,17 +98,7 @@
/// A metadata entry for key entries.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum KeyMetaEntry {
- /// If present, indicates that the sensitive part of key
- /// is encrypted with another key or a key derived from a password.
- EncryptedBy(EncryptedBy) with accessor encrypted_by,
- /// If the blob is password encrypted this field is set to the
- /// salt used for the key derivation.
- Salt(Vec<u8>) with accessor salt,
- /// If the blob is encrypted, this field is set to the initialization vector.
- Iv(Vec<u8>) with accessor iv,
- /// If the blob is encrypted, this field holds the AEAD TAG.
- AeadTag(Vec<u8>) with accessor aead_tag,
- /// Creation date of a the key entry.
+ /// Date of the creation of the key entry.
CreationDate(DateTime) with accessor creation_date,
/// Expiration date for attestation keys.
AttestationExpirationDate(DateTime) with accessor attestation_expiration_date,
@@ -151,7 +144,7 @@
fn store_in_db(&self, key_id: i64, tx: &Transaction) -> Result<()> {
let mut stmt = tx
.prepare(
- "INSERT into persistent.keymetadata (keyentryid, tag, data)
+ "INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data)
VALUES (?, ?, ?);",
)
.context("In KeyMetaData::store_in_db: Failed to prepare statement.")?;
@@ -166,6 +159,76 @@
}
}
+impl_metadata!(
+ /// A set of metadata for key blobs.
+ #[derive(Debug, Default, Eq, PartialEq)]
+ pub struct BlobMetaData;
+ /// A metadata entry for key blobs.
+ #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
+ pub enum BlobMetaEntry {
+ /// If present, indicates that the blob is encrypted with another key or a key derived
+ /// from a password.
+ EncryptedBy(EncryptedBy) with accessor encrypted_by,
+ /// If the blob is password encrypted this field is set to the
+ /// salt used for the key derivation.
+ Salt(Vec<u8>) with accessor salt,
+ /// If the blob is encrypted, this field is set to the initialization vector.
+ Iv(Vec<u8>) with accessor iv,
+ /// If the blob is encrypted, this field holds the AEAD TAG.
+ AeadTag(Vec<u8>) with accessor aead_tag,
+ /// The uuid of the owning KeyMint instance.
+ KmUuid(Uuid) with accessor km_uuid,
+ // --- ADD NEW META DATA FIELDS HERE ---
+ // For backwards compatibility add new entries only to
+ // end of this list and above this comment.
+ };
+);
+
+impl BlobMetaData {
+ fn load_from_db(blob_id: i64, tx: &Transaction) -> Result<Self> {
+ let mut stmt = tx
+ .prepare(
+ "SELECT tag, data from persistent.blobmetadata
+ WHERE blobentryid = ?;",
+ )
+ .context("In BlobMetaData::load_from_db: prepare statement failed.")?;
+
+ let mut metadata: HashMap<i64, BlobMetaEntry> = Default::default();
+
+ let mut rows =
+ stmt.query(params![blob_id]).context("In BlobMetaData::load_from_db: query failed.")?;
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ let db_tag: i64 = row.get(0).context("Failed to read tag.")?;
+ metadata.insert(
+ db_tag,
+ BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, &row))
+ .context("Failed to read BlobMetaEntry.")?,
+ );
+ Ok(())
+ })
+ .context("In BlobMetaData::load_from_db.")?;
+
+ Ok(Self { data: metadata })
+ }
+
+ fn store_in_db(&self, blob_id: i64, tx: &Transaction) -> Result<()> {
+ let mut stmt = tx
+ .prepare(
+ "INSERT or REPLACE INTO persistent.blobmetadata (blobentryid, tag, data)
+ VALUES (?, ?, ?);",
+ )
+ .context("In BlobMetaData::store_in_db: Failed to prepare statement.")?;
+
+ let iter = self.data.iter();
+ for (tag, entry) in iter {
+ stmt.insert(params![blob_id, tag, entry,]).with_context(|| {
+ format!("In BlobMetaData::store_in_db: Failed to insert {:?}", entry)
+ })?;
+ }
+ Ok(())
+ }
+}
+
/// Indicates the type of the keyentry.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum KeyType {
@@ -518,10 +581,14 @@
/// This type represents a certificate chain with a private key corresponding to the leaf
/// certificate. TODO(jbires): This will be used in a follow-on CL, for now it's used in the tests.
-#[allow(dead_code)]
pub struct CertificateChain {
- private_key: ZVec,
- cert_chain: ZVec,
+ /// A KM key blob
+ pub private_key: ZVec,
+ /// A batch cert for private_key
+ pub batch_cert: Vec<u8>,
+ /// A full certificate chain from root signing authority to private_key, including batch_cert
+ /// for convenience.
+ pub cert_chain: Vec<u8>,
}
/// This type represents a Keystore 2.0 key entry.
@@ -531,7 +598,7 @@
#[derive(Debug, Default, Eq, PartialEq)]
pub struct KeyEntry {
id: i64,
- km_blob: Option<Vec<u8>>,
+ key_blob_info: Option<(Vec<u8>, BlobMetaData)>,
cert: Option<Vec<u8>>,
cert_chain: Option<Vec<u8>>,
km_uuid: Uuid,
@@ -546,12 +613,12 @@
self.id
}
/// Exposes the optional KeyMint blob.
- pub fn km_blob(&self) -> &Option<Vec<u8>> {
- &self.km_blob
+ pub fn key_blob_info(&self) -> &Option<(Vec<u8>, BlobMetaData)> {
+ &self.key_blob_info
}
- /// Extracts the Optional KeyMint blob.
- pub fn take_km_blob(&mut self) -> Option<Vec<u8>> {
- self.km_blob.take()
+ /// Extracts the Optional KeyMint blob including its metadata.
+ pub fn take_key_blob_info(&mut self) -> Option<(Vec<u8>, BlobMetaData)> {
+ self.key_blob_info.take()
}
/// Exposes the optional public certificate.
pub fn cert(&self) -> &Option<Vec<u8>> {
@@ -590,6 +657,10 @@
pub fn pure_cert(&self) -> bool {
self.pure_cert
}
+ /// Consumes this key entry and extracts the keyparameters and metadata from it.
+ pub fn into_key_parameters_and_metadata(self) -> (Vec<KeyParameter>, KeyMetaData) {
+ (self.parameters, self.metadata)
+ }
}
/// Indicates the sub component of a key entry for persistent storage.
@@ -616,10 +687,39 @@
}
}
+/// This trait is private to the database module. It is used to convey whether or not the garbage
+/// collector shall be invoked after a database access. All closures passed to
+/// `KeystoreDB::with_transaction` return a tuple (bool, T) where the bool indicates if the
+/// gc needs to be triggered. This convenience function allows to turn any anyhow::Result<T>
+/// into anyhow::Result<(bool, T)> by simply appending one of `.do_gc(bool)`, `.no_gc()`, or
+/// `.need_gc()`.
+trait DoGc<T> {
+ fn do_gc(self, need_gc: bool) -> Result<(bool, T)>;
+
+ fn no_gc(self) -> Result<(bool, T)>;
+
+ fn need_gc(self) -> Result<(bool, T)>;
+}
+
+impl<T> DoGc<T> for Result<T> {
+ fn do_gc(self, need_gc: bool) -> Result<(bool, T)> {
+ self.map(|r| (need_gc, r))
+ }
+
+ fn no_gc(self) -> Result<(bool, T)> {
+ self.do_gc(false)
+ }
+
+ fn need_gc(self) -> Result<(bool, T)> {
+ self.do_gc(true)
+ }
+}
+
/// KeystoreDB wraps a connection to an SQLite database and tracks its
/// ownership. It also implements all of Keystore 2.0's database functionality.
pub struct KeystoreDB {
conn: Connection,
+ gc: Option<Gc>,
}
/// Database representation of the monotonic time retrieved from the system call clock_gettime with
@@ -698,8 +798,12 @@
pub struct PerBootDbKeepAlive(Connection);
impl KeystoreDB {
+ const UNASSIGNED_KEY_ID: i64 = -1i64;
const PERBOOT_DB_FILE_NAME: &'static str = &"file:perboot.sqlite?mode=memory&cache=shared";
+ /// The alias of the user super key.
+ pub const USER_SUPER_KEY_ALIAS: &'static str = &"USER_SUPER_KEY";
+
/// This creates a PerBootDbKeepAlive object to keep the per boot database alive.
pub fn keep_perboot_db_alive() -> Result<PerBootDbKeepAlive> {
let conn = Connection::open_in_memory()
@@ -715,7 +819,7 @@
/// It also attempts to initialize all of the tables.
/// KeystoreDB cannot be used by multiple threads.
/// Each thread should open their own connection using `thread_local!`.
- pub fn new(db_root: &Path) -> Result<Self> {
+ pub fn new(db_root: &Path, gc: Option<Gc>) -> Result<Self> {
// Build the path to the sqlite file.
let mut persistent_path = db_root.to_path_buf();
persistent_path.push("persistent.sqlite");
@@ -729,9 +833,9 @@
// On busy fail Immediately. It is unlikely to succeed given a bug in sqlite.
conn.busy_handler(None).context("In KeystoreDB::new: Failed to set busy handler.")?;
- let mut db = Self { conn };
+ let mut db = Self { conn, gc };
db.with_transaction(TransactionBehavior::Immediate, |tx| {
- Self::init_tables(tx).context("Trying to initialize tables.")
+ Self::init_tables(tx).context("Trying to initialize tables.").no_gc()
})?;
Ok(db)
}
@@ -782,6 +886,24 @@
.context("Failed to create index blobentry_keyentryid_index.")?;
tx.execute(
+ "CREATE TABLE IF NOT EXISTS persistent.blobmetadata (
+ id INTEGER PRIMARY KEY,
+ blobentryid INTEGER,
+ tag INTEGER,
+ data ANY,
+ UNIQUE (blobentryid, tag));",
+ NO_PARAMS,
+ )
+ .context("Failed to initialize \"blobmetadata\" table.")?;
+
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.blobmetadata_blobentryid_index
+ ON blobmetadata(blobentryid);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index blobmetadata_blobentryid_index.")?;
+
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.keyparameter (
keyentryid INTEGER,
tag INTEGER,
@@ -802,7 +924,8 @@
"CREATE TABLE IF NOT EXISTS persistent.keymetadata (
keyentryid INTEGER,
tag INTEGER,
- data ANY);",
+ data ANY,
+ UNIQUE (keyentryid, tag));",
NO_PARAMS,
)
.context("Failed to initialize \"keymetadata\" table.")?;
@@ -894,77 +1017,73 @@
Ok(conn)
}
- /// Get one unreferenced key. There is no particular order in which the keys are returned.
- fn get_unreferenced_key_id(tx: &Transaction) -> Result<Option<i64>> {
- tx.query_row(
- "SELECT id FROM persistent.keyentry WHERE state = ?",
- params![KeyLifeCycle::Unreferenced],
- |row| row.get(0),
- )
- .optional()
- .context("In get_unreferenced_key_id: Trying to get unreferenced key id.")
- }
-
- /// Returns a key id guard and key entry for one unreferenced key entry. Of the optional
- /// fields of the key entry only the km_blob field will be populated. This is required
- /// to subject the blob to its KeyMint instance for deletion.
- pub fn get_unreferenced_key(&mut self) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
- self.with_transaction(TransactionBehavior::Deferred, |tx| {
- let key_id = match Self::get_unreferenced_key_id(tx)
- .context("Trying to get unreferenced key id")?
- {
- None => return Ok(None),
- Some(id) => KEY_ID_LOCK.try_get(id).ok_or_else(KsError::sys).context(concat!(
- "A key id lock was held for an unreferenced key. ",
- "This should never happen."
- ))?,
- };
- let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, key_id.id())
- .context("Trying to get key components.")?;
- Ok(Some((key_id, key_entry)))
- })
- .context("In get_unreferenced_key.")
- }
-
- /// This function purges all remnants of a key entry from the database.
- /// Important: This does not check if the key was unreferenced, nor does it
- /// subject the key to its KeyMint instance for permanent invalidation.
- /// This function should only be called by the garbage collector.
- /// To delete a key call `mark_unreferenced`, which transitions the key to the unreferenced
- /// state, deletes all grants to the key, and notifies the garbage collector.
- /// The garbage collector will:
- /// 1. Call get_unreferenced_key.
- /// 2. Determine the proper way to dispose of sensitive key material, e.g., call
- /// `KeyMintDevice::delete()`.
- /// 3. Call `purge_key_entry`.
- pub fn purge_key_entry(&mut self, key_id: KeyIdGuard) -> Result<()> {
+ /// This function is intended to be used by the garbage collector.
+ /// It deletes the blob given by `blob_id_to_delete`. It then tries to find a superseded
+ /// key blob that might need special handling by the garbage collector.
+ /// If no further superseded blobs can be found it deletes all other superseded blobs that don't
+ /// need special handling and returns None.
+ pub fn handle_next_superseded_blob(
+ &mut self,
+ blob_id_to_delete: Option<i64>,
+ ) -> Result<Option<(i64, Vec<u8>, BlobMetaData)>> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- tx.execute("DELETE FROM persistent.keyentry WHERE id = ?;", params![key_id.id()])
- .context("Trying to delete keyentry.")?;
- tx.execute(
- "DELETE FROM persistent.blobentry WHERE keyentryid = ?;",
- params![key_id.id()],
- )
- .context("Trying to delete blobentries.")?;
- tx.execute(
- "DELETE FROM persistent.keymetadata WHERE keyentryid = ?;",
- params![key_id.id()],
- )
- .context("Trying to delete keymetadata.")?;
- tx.execute(
- "DELETE FROM persistent.keyparameter WHERE keyentryid = ?;",
- params![key_id.id()],
- )
- .context("Trying to delete keyparameters.")?;
- let grants_deleted = tx
- .execute("DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id.id()])
- .context("Trying to delete grants.")?;
- if grants_deleted != 0 {
- log::error!("Purged key that still had grants. This should not happen.");
+ // Delete the given blob if one was given.
+ if let Some(blob_id_to_delete) = blob_id_to_delete {
+ tx.execute(
+ "DELETE FROM persistent.blobmetadata WHERE blobentryid = ?;",
+ params![blob_id_to_delete],
+ )
+ .context("Trying to delete blob metadata.")?;
+ tx.execute(
+ "DELETE FROM persistent.blobentry WHERE id = ?;",
+ params![blob_id_to_delete],
+ )
+ .context("Trying to blob.")?;
}
- Ok(())
+
+ // Find another superseded keyblob load its metadata and return it.
+ if let Some((blob_id, blob)) = tx
+ .query_row(
+ "SELECT id, blob FROM persistent.blobentry
+ WHERE subcomponent_type = ?
+ AND (
+ id NOT IN (
+ SELECT MAX(id) FROM persistent.blobentry
+ WHERE subcomponent_type = ?
+ GROUP BY keyentryid, subcomponent_type
+ )
+ OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
+ );",
+ params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ )
+ .optional()
+ .context("Trying to query superseded blob.")?
+ {
+ let blob_metadata = BlobMetaData::load_from_db(blob_id, tx)
+ .context("Trying to load blob metadata.")?;
+ return Ok(Some((blob_id, blob, blob_metadata))).no_gc();
+ }
+
+ // We did not find any superseded key blob, so let's remove other superseded blob in
+ // one transaction.
+ tx.execute(
+ "DELETE FROM persistent.blobentry
+ WHERE NOT subcomponent_type = ?
+ AND (
+ id NOT IN (
+ SELECT MAX(id) FROM persistent.blobentry
+ WHERE NOT subcomponent_type = ?
+ GROUP BY keyentryid, subcomponent_type
+ ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
+ );",
+ params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
+ )
+ .context("Trying to purge superseded blobs.")?;
+
+ Ok(None).no_gc()
})
- .context("In purge_key_entry.")
+ .context("In handle_next_superseded_blob.")
}
/// This maintenance function should be called only once before the database is used for the
@@ -982,10 +1101,103 @@
params![KeyLifeCycle::Unreferenced, KeyLifeCycle::Existing],
)
.context("Failed to execute query.")
+ .need_gc()
})
.context("In cleanup_leftovers.")
}
+ /// Checks if a key exists with given key type and key descriptor properties.
+ pub fn key_exists(
+ &mut self,
+ domain: Domain,
+ nspace: i64,
+ alias: &str,
+ key_type: KeyType,
+ ) -> Result<bool> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_descriptor =
+ KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None };
+ let result = Self::load_key_entry_id(&tx, &key_descriptor, key_type);
+ match result {
+ Ok(_) => Ok(true),
+ Err(error) => match error.root_cause().downcast_ref::<KsError>() {
+ Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(false),
+ _ => Err(error).context("In key_exists: Failed to find if the key exists."),
+ },
+ }
+ .no_gc()
+ })
+ .context("In key_exists.")
+ }
+
+ /// Stores a super key in the database.
+ pub fn store_super_key(
+ &mut self,
+ user_id: u32,
+ blob_info: &(&[u8], &BlobMetaData),
+ ) -> Result<KeyEntry> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_id = Self::insert_with_retry(|id| {
+ tx.execute(
+ "INSERT into persistent.keyentry
+ (id, key_type, domain, namespace, alias, state, km_uuid)
+ VALUES(?, ?, ?, ?, ?, ?, ?);",
+ params![
+ id,
+ KeyType::Super,
+ Domain::APP.0,
+ user_id as i64,
+ Self::USER_SUPER_KEY_ALIAS,
+ KeyLifeCycle::Live,
+ &KEYSTORE_UUID,
+ ],
+ )
+ })
+ .context("Failed to insert into keyentry table.")?;
+
+ let (blob, blob_metadata) = *blob_info;
+ Self::set_blob_internal(
+ &tx,
+ key_id,
+ SubComponentType::KEY_BLOB,
+ Some(blob),
+ Some(blob_metadata),
+ )
+ .context("Failed to store key blob.")?;
+
+ Self::load_key_components(tx, KeyEntryLoadBits::KM, key_id)
+ .context("Trying to load key components.")
+ .no_gc()
+ })
+ .context("In store_super_key.")
+ }
+
+ /// Loads super key of a given user, if exists
+ pub fn load_super_key(&mut self, user_id: u32) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_descriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: user_id as i64,
+ alias: Some(String::from("USER_SUPER_KEY")),
+ blob: None,
+ };
+ let id = Self::load_key_entry_id(&tx, &key_descriptor, KeyType::Super);
+ match id {
+ Ok(id) => {
+ let key_entry = Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
+ .context("In load_super_key. Failed to load key entry.")?;
+ Ok(Some((KEY_ID_LOCK.get(id), key_entry)))
+ }
+ Err(error) => match error.root_cause().downcast_ref::<KsError>() {
+ Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
+ _ => Err(error).context("In load_super_key."),
+ },
+ }
+ .no_gc()
+ })
+ .context("In load_super_key.")
+ }
+
/// Atomically loads a key entry and associated metadata or creates it using the
/// callback create_new_key callback. The callback is called during a database
/// transaction. This means that implementers should be mindful about using
@@ -999,7 +1211,7 @@
create_new_key: F,
) -> Result<(KeyIdGuard, KeyEntry)>
where
- F: Fn() -> Result<(Vec<u8>, KeyMetaData)>,
+ F: Fn() -> Result<(Vec<u8>, BlobMetaData)>,
{
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let id = {
@@ -1055,22 +1267,26 @@
let (blob, metadata) =
create_new_key().context("In get_or_create_key_with.")?;
- Self::set_blob_internal(&tx, id, SubComponentType::KEY_BLOB, Some(&blob))
- .context("In get_of_create_key_with.")?;
- metadata.store_in_db(id, &tx).context("In get_or_create_key_with.")?;
+ Self::set_blob_internal(
+ &tx,
+ id,
+ SubComponentType::KEY_BLOB,
+ Some(&blob),
+ Some(&metadata),
+ )
+ .context("In get_of_create_key_with.")?;
(
id,
KeyEntry {
id,
- km_blob: Some(blob),
- metadata,
+ key_blob_info: Some((blob, metadata)),
pure_cert: false,
..Default::default()
},
)
}
};
- Ok((KEY_ID_LOCK.get(id), entry))
+ Ok((KEY_ID_LOCK.get(id), entry)).no_gc()
})
.context("In get_or_create_key_with.")
}
@@ -1092,7 +1308,7 @@
/// or DatabaseLocked is encountered.
fn with_transaction<T, F>(&mut self, behavior: TransactionBehavior, f: F) -> Result<T>
where
- F: Fn(&Transaction) -> Result<T>,
+ F: Fn(&Transaction) -> Result<(bool, T)>,
{
loop {
match self
@@ -1115,6 +1331,14 @@
}
}
}
+ .map(|(need_gc, result)| {
+ if need_gc {
+ if let Some(ref gc) = self.gc {
+ gc.notify_gc();
+ }
+ }
+ result
+ })
}
fn is_locked_error(e: &anyhow::Error) -> bool {
@@ -1142,7 +1366,7 @@
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- Self::create_key_entry_internal(tx, domain, namespace, km_uuid)
+ Self::create_key_entry_internal(tx, domain, namespace, km_uuid).no_gc()
})
.context("In create_key_entry.")
}
@@ -1204,12 +1428,18 @@
})
.context("In create_key_entry")?,
);
- Self::set_blob_internal(&tx, key_id.0, SubComponentType::KEY_BLOB, Some(private_key))?;
+ Self::set_blob_internal(
+ &tx,
+ key_id.0,
+ SubComponentType::KEY_BLOB,
+ Some(private_key),
+ None,
+ )?;
let mut metadata = KeyMetaData::new();
metadata.add(KeyMetaEntry::AttestationMacedPublicKey(maced_public_key.to_vec()));
metadata.add(KeyMetaEntry::AttestationRawPubKey(raw_public_key.to_vec()));
metadata.store_in_db(key_id.0, &tx)?;
- Ok(())
+ Ok(()).no_gc()
})
.context("In create_attestation_key_entry")
}
@@ -1226,18 +1456,38 @@
key_id: &KeyIdGuard,
sc_type: SubComponentType,
blob: Option<&[u8]>,
+ blob_metadata: Option<&BlobMetaData>,
) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- Self::set_blob_internal(&tx, key_id.0, sc_type, blob)
+ Self::set_blob_internal(&tx, key_id.0, sc_type, blob, blob_metadata).need_gc()
})
.context("In set_blob.")
}
+ /// Why would we insert a deleted blob? This weird function is for the purpose of legacy
+ /// key migration in the case where we bulk delete all the keys of an app or even a user.
+ /// We use this to insert key blobs into the database which can then be garbage collected
+ /// lazily by the key garbage collector.
+ pub fn set_deleted_blob(&mut self, blob: &[u8], blob_metadata: &BlobMetaData) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ Self::set_blob_internal(
+ &tx,
+ Self::UNASSIGNED_KEY_ID,
+ SubComponentType::KEY_BLOB,
+ Some(blob),
+ Some(blob_metadata),
+ )
+ .need_gc()
+ })
+ .context("In set_deleted_blob.")
+ }
+
fn set_blob_internal(
tx: &Transaction,
key_id: i64,
sc_type: SubComponentType,
blob: Option<&[u8]>,
+ blob_metadata: Option<&BlobMetaData>,
) -> Result<()> {
match (blob, sc_type) {
(Some(blob), _) => {
@@ -1247,6 +1497,16 @@
params![sc_type, key_id, blob],
)
.context("In set_blob_internal: Failed to insert blob.")?;
+ if let Some(blob_metadata) = blob_metadata {
+ let blob_id = tx
+ .query_row("SELECT MAX(id) FROM persistent.blobentry;", NO_PARAMS, |row| {
+ row.get(0)
+ })
+ .context("In set_blob_internal: Failed to get new blob id.")?;
+ blob_metadata
+ .store_in_db(blob_id, tx)
+ .context("In set_blob_internal: Trying to store blob metadata.")?;
+ }
}
(None, SubComponentType::CERT) | (None, SubComponentType::CERT_CHAIN) => {
tx.execute(
@@ -1266,13 +1526,10 @@
/// Inserts a collection of key parameters into the `persistent.keyparameter` table
/// and associates them with the given `key_id`.
- pub fn insert_keyparameter(
- &mut self,
- key_id: &KeyIdGuard,
- params: &[KeyParameter],
- ) -> Result<()> {
+ #[cfg(test)]
+ fn insert_keyparameter(&mut self, key_id: &KeyIdGuard, params: &[KeyParameter]) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- Self::insert_keyparameter_internal(tx, key_id, params)
+ Self::insert_keyparameter_internal(tx, key_id, params).no_gc()
})
.context("In insert_keyparameter.")
}
@@ -1304,13 +1561,10 @@
}
/// Insert a set of key entry specific metadata into the database.
- pub fn insert_key_metadata(
- &mut self,
- key_id: &KeyIdGuard,
- metadata: &KeyMetaData,
- ) -> Result<()> {
+ #[cfg(test)]
+ fn insert_key_metadata(&mut self, key_id: &KeyIdGuard, metadata: &KeyMetaData) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- metadata.store_in_db(key_id.0, &tx)
+ metadata.store_in_db(key_id.0, &tx).no_gc()
})
.context("In insert_key_metadata.")
}
@@ -1320,6 +1574,7 @@
pub fn store_signed_attestation_certificate_chain(
&mut self,
raw_public_key: &[u8],
+ batch_cert: &[u8],
cert_chain: &[u8],
expiration_date: i64,
km_uuid: &Uuid,
@@ -1370,9 +1625,17 @@
expiration_date,
)));
metadata.store_in_db(key_id, &tx).context("Failed to insert key metadata.")?;
- Self::set_blob_internal(&tx, key_id, SubComponentType::CERT_CHAIN, Some(cert_chain))
- .context("Failed to insert cert chain")?;
- Ok(())
+ Self::set_blob_internal(
+ &tx,
+ key_id,
+ SubComponentType::CERT_CHAIN,
+ Some(cert_chain),
+ None,
+ )
+ .context("Failed to insert cert chain")?;
+ Self::set_blob_internal(&tx, key_id, SubComponentType::CERT, Some(batch_cert), None)
+ .context("Failed to insert cert")?;
+ Ok(()).no_gc()
})
.context("In store_signed_attestation_certificate_chain: ")
}
@@ -1434,7 +1697,7 @@
result
));
}
- Ok(())
+ Ok(()).no_gc()
})
.context("In assign_attestation_key: ")
}
@@ -1476,7 +1739,7 @@
)?
.collect::<rusqlite::Result<Vec<Vec<u8>>>>()
.context("Failed to execute statement")?;
- Ok(rows)
+ Ok(rows).no_gc()
})
.context("In fetch_unsigned_attestation_keys")
}
@@ -1511,7 +1774,7 @@
num_deleted += 1;
}
}
- Ok(num_deleted)
+ Ok(num_deleted).do_gc(num_deleted != 0)
})
.context("In delete_expired_attestation_keys: ")
}
@@ -1576,7 +1839,7 @@
_ => {}
}
}
- Ok(AttestationPoolStatus { expiring, unassigned, attested, total })
+ Ok(AttestationPoolStatus { expiring, unassigned, attested, total }).no_gc()
})
.context("In get_attestation_pool_status: ")
}
@@ -1597,8 +1860,9 @@
.context(format!("Domain {:?} must be either App or SELinux.", domain));
}
}
- let mut stmt = self.conn.prepare(
- "SELECT subcomponent_type, blob
+ self.with_transaction(TransactionBehavior::Deferred, |tx| {
+ let mut stmt = tx.prepare(
+ "SELECT subcomponent_type, blob
FROM persistent.blobentry
WHERE keyentryid IN
(SELECT id
@@ -1608,48 +1872,57 @@
AND namespace = ?
AND state = ?
AND km_uuid = ?);",
- )?;
- let rows = stmt
- .query_map(
- params![
- KeyType::Attestation,
- domain.0 as u32,
- namespace,
- KeyLifeCycle::Live,
- km_uuid
- ],
- |row| Ok((row.get(0)?, row.get(1)?)),
- )?
- .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>()
- .context("In retrieve_attestation_key_and_cert_chain: query failed.")?;
- if rows.is_empty() {
- return Ok(None);
- } else if rows.len() != 2 {
- return Err(KsError::sys()).context(format!(
- concat!(
- "In retrieve_attestation_key_and_cert_chain: Expected to get a single attestation",
- "key chain but instead got {}."),
- rows.len()
- ));
- }
- let mut km_blob: Vec<u8> = Vec::new();
- let mut cert_chain_blob: Vec<u8> = Vec::new();
- for row in rows {
- let sub_type: SubComponentType = row.0;
- match sub_type {
- SubComponentType::KEY_BLOB => {
- km_blob = row.1;
- }
- SubComponentType::CERT_CHAIN => {
- cert_chain_blob = row.1;
- }
- _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?,
+ )?;
+ let rows = stmt
+ .query_map(
+ params![
+ KeyType::Attestation,
+ domain.0 as u32,
+ namespace,
+ KeyLifeCycle::Live,
+ km_uuid
+ ],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ )?
+ .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>()
+ .context("query failed.")?;
+ if rows.is_empty() {
+ return Ok(None).no_gc();
+ } else if rows.len() != 3 {
+ return Err(KsError::sys()).context(format!(
+ concat!(
+ "Expected to get a single attestation",
+ "key, cert, and cert chain for a total of 3 entries, but instead got {}."
+ ),
+ rows.len()
+ ));
}
- }
- Ok(Some(CertificateChain {
- private_key: ZVec::try_from(km_blob)?,
- cert_chain: ZVec::try_from(cert_chain_blob)?,
- }))
+ let mut km_blob: Vec<u8> = Vec::new();
+ let mut cert_chain_blob: Vec<u8> = Vec::new();
+ let mut batch_cert_blob: Vec<u8> = Vec::new();
+ for row in rows {
+ let sub_type: SubComponentType = row.0;
+ match sub_type {
+ SubComponentType::KEY_BLOB => {
+ km_blob = row.1;
+ }
+ SubComponentType::CERT_CHAIN => {
+ cert_chain_blob = row.1;
+ }
+ SubComponentType::CERT => {
+ batch_cert_blob = row.1;
+ }
+ _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?,
+ }
+ }
+ Ok(Some(CertificateChain {
+ private_key: ZVec::try_from(km_blob)?,
+ batch_cert: batch_cert_blob,
+ cert_chain: cert_chain_blob,
+ }))
+ .no_gc()
+ })
+ .context("In retrieve_attestation_key_and_cert_chain:")
}
/// Updates the alias column of the given key id `newid` with the given alias,
@@ -1714,11 +1987,11 @@
&mut self,
key: &KeyDescriptor,
params: &[KeyParameter],
- blob: &[u8],
+ blob_info: &(&[u8], &BlobMetaData),
cert_info: &CertificateInfo,
metadata: &KeyMetaData,
km_uuid: &Uuid,
- ) -> Result<(bool, KeyIdGuard)> {
+ ) -> Result<KeyIdGuard> {
let (alias, domain, namespace) = match key {
KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None }
| KeyDescriptor { alias: Some(alias), domain: Domain::SELINUX, nspace, blob: None } => {
@@ -1732,10 +2005,17 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_id = Self::create_key_entry_internal(tx, &domain, namespace, km_uuid)
.context("Trying to create new key entry.")?;
- Self::set_blob_internal(tx, key_id.id(), SubComponentType::KEY_BLOB, Some(blob))
- .context("Trying to insert the key blob.")?;
+ let (blob, blob_metadata) = *blob_info;
+ Self::set_blob_internal(
+ tx,
+ key_id.id(),
+ SubComponentType::KEY_BLOB,
+ Some(blob),
+ Some(&blob_metadata),
+ )
+ .context("Trying to insert the key blob.")?;
if let Some(cert) = &cert_info.cert {
- Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(&cert))
+ Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(&cert), None)
.context("Trying to insert the certificate.")?;
}
if let Some(cert_chain) = &cert_info.cert_chain {
@@ -1744,6 +2024,7 @@
key_id.id(),
SubComponentType::CERT_CHAIN,
Some(&cert_chain),
+ None,
)
.context("Trying to insert the certificate chain.")?;
}
@@ -1752,7 +2033,7 @@
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace)
.context("Trying to rebind alias.")?;
- Ok((need_gc, key_id))
+ Ok(key_id).do_gc(need_gc)
})
.context("In store_new_key.")
}
@@ -1781,8 +2062,14 @@
let key_id = Self::create_key_entry_internal(tx, &domain, namespace, km_uuid)
.context("Trying to create new key entry.")?;
- Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT_CHAIN, Some(cert))
- .context("Trying to insert certificate.")?;
+ Self::set_blob_internal(
+ tx,
+ key_id.id(),
+ SubComponentType::CERT_CHAIN,
+ Some(cert),
+ None,
+ )
+ .context("Trying to insert certificate.")?;
let mut metadata = KeyMetaData::new();
metadata.add(KeyMetaEntry::CreationDate(
@@ -1791,9 +2078,9 @@
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
- Self::rebind_alias(tx, &key_id, &alias, &domain, namespace)
+ let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace)
.context("Trying to rebind alias.")?;
- Ok(key_id)
+ Ok(key_id).do_gc(need_gc)
})
.context("In store_new_certificate.")
}
@@ -1811,7 +2098,7 @@
.prepare(
"SELECT id FROM persistent.keyentry
WHERE
- key_type = ?
+ key_type = ?
AND domain = ?
AND namespace = ?
AND alias = ?
@@ -1954,7 +2241,7 @@
key_id: i64,
load_bits: KeyEntryLoadBits,
tx: &Transaction,
- ) -> Result<(bool, Option<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>)> {
+ ) -> Result<(bool, Option<(Vec<u8>, BlobMetaData)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
let mut stmt = tx
.prepare(
"SELECT MAX(id), subcomponent_type, blob FROM persistent.blobentry
@@ -1965,7 +2252,7 @@
let mut rows =
stmt.query(params![key_id]).context("In load_blob_components: query failed.")?;
- let mut km_blob: Option<Vec<u8>> = None;
+ let mut key_blob: Option<(i64, Vec<u8>)> = None;
let mut cert_blob: Option<Vec<u8>> = None;
let mut cert_chain_blob: Option<Vec<u8>> = None;
let mut has_km_blob: bool = false;
@@ -1975,7 +2262,10 @@
has_km_blob = has_km_blob || sub_type == SubComponentType::KEY_BLOB;
match (sub_type, load_bits.load_public(), load_bits.load_km()) {
(SubComponentType::KEY_BLOB, _, true) => {
- km_blob = Some(row.get(2).context("Failed to extract KM blob.")?);
+ key_blob = Some((
+ row.get(0).context("Failed to extract key blob id.")?,
+ row.get(2).context("Failed to extract key blob.")?,
+ ));
}
(SubComponentType::CERT, true, _) => {
cert_blob =
@@ -1994,7 +2284,15 @@
})
.context("In load_blob_components.")?;
- Ok((has_km_blob, km_blob, cert_blob, cert_chain_blob))
+ let blob_info = key_blob.map_or::<Result<_>, _>(Ok(None), |(blob_id, blob)| {
+ Ok(Some((
+ blob,
+ BlobMetaData::load_from_db(blob_id, tx)
+ .context("In load_blob_components: Trying to load blob_metadata.")?,
+ )))
+ })?;
+
+ Ok((has_km_blob, blob_info, cert_blob, cert_chain_blob))
}
fn load_key_parameters(key_id: i64, tx: &Transaction) -> Result<Vec<KeyParameter>> {
@@ -2027,7 +2325,7 @@
/// usage has been exhausted, if not, decreases the usage count. If the usage count reaches
/// zero, the key also gets marked unreferenced and scheduled for deletion.
/// Returns Ok(true) if the key was marked unreferenced as a hint to the garbage collector.
- pub fn check_and_update_key_usage_count(&mut self, key_id: i64) -> Result<bool> {
+ pub fn check_and_update_key_usage_count(&mut self, key_id: i64) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let limit: Option<i32> = tx
.query_row(
@@ -2052,9 +2350,10 @@
match limit {
1 => Self::mark_unreferenced(tx, key_id)
+ .map(|need_gc| (need_gc, ()))
.context("Trying to mark limited use key for deletion."),
0 => Err(KsError::Km(ErrorCode::INVALID_KEY_BLOB)).context("Key is exhausted."),
- _ => Ok(false),
+ _ => Ok(()).no_gc(),
}
})
.context("In check_and_update_key_usage_count.")
@@ -2180,13 +2479,14 @@
fn mark_unreferenced(tx: &Transaction, key_id: i64) -> Result<bool> {
let updated = tx
- .execute(
- "UPDATE persistent.keyentry SET state = ? WHERE id = ?;",
- params![KeyLifeCycle::Unreferenced, key_id],
- )
- .context("In mark_unreferenced: Failed to update state of key entry.")?;
- tx.execute("DELETE from persistent.grant WHERE keyentryid = ?;", params![key_id])
- .context("In mark_unreferenced: Failed to drop grants.")?;
+ .execute("DELETE FROM persistent.keyentry WHERE id = ?;", params![key_id])
+ .context("Trying to delete keyentry.")?;
+ tx.execute("DELETE FROM persistent.keymetadata WHERE keyentryid = ?;", params![key_id])
+ .context("Trying to delete keymetadata.")?;
+ tx.execute("DELETE FROM persistent.keyparameter WHERE keyentryid = ?;", params![key_id])
+ .context("Trying to delete keyparameters.")?;
+ tx.execute("DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id])
+ .context("Trying to delete grants.")?;
Ok(updated != 0)
}
@@ -2198,7 +2498,7 @@
key_type: KeyType,
caller_uid: u32,
check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
- ) -> Result<bool> {
+ ) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let (key_id, access_key_descriptor, access_vector) =
Self::load_access_tuple(tx, key, key_type, caller_uid)
@@ -2209,7 +2509,9 @@
check_permission(&access_key_descriptor, access_vector)
.context("While checking permission.")?;
- Self::mark_unreferenced(tx, key_id).context("Trying to mark the key unreferenced.")
+ Self::mark_unreferenced(tx, key_id)
+ .map(|need_gc| (need_gc, ()))
+ .context("Trying to mark the key unreferenced.")
})
.context("In unbind_key.")
}
@@ -2223,6 +2525,127 @@
.context("In get_key_km_uuid.")
}
+ /// Delete all artifacts belonging to the namespace given by the domain-namespace tuple.
+ /// This leaves all of the blob entries orphaned for subsequent garbage collection.
+ pub fn unbind_keys_for_namespace(&mut self, domain: Domain, namespace: i64) -> Result<()> {
+ if !(domain == Domain::APP || domain == Domain::SELINUX) {
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("In unbind_keys_for_namespace.");
+ }
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
+ "DELETE FROM persistent.keymetadata
+ WHERE keyentryid IN (
+ SELECT id FROM persistent.keyentry
+ WHERE domain = ? AND namespace = ?
+ );",
+ params![domain.0, namespace],
+ )
+ .context("Trying to delete keymetadata.")?;
+ tx.execute(
+ "DELETE FROM persistent.keyparameter
+ WHERE keyentryid IN (
+ SELECT id FROM persistent.keyentry
+ WHERE domain = ? AND namespace = ?
+ );",
+ params![domain.0, namespace],
+ )
+ .context("Trying to delete keyparameters.")?;
+ tx.execute(
+ "DELETE FROM persistent.grant
+ WHERE keyentryid IN (
+ SELECT id FROM persistent.keyentry
+ WHERE domain = ? AND namespace = ?
+ );",
+ params![domain.0, namespace],
+ )
+ .context("Trying to delete grants.")?;
+ tx.execute(
+ "DELETE FROM persistent.keyentry WHERE domain = ? AND namespace = ?;",
+ params![domain.0, namespace],
+ )
+ .context("Trying to delete keyentry.")?;
+ Ok(()).need_gc()
+ })
+ .context("In unbind_keys_for_namespace")
+ }
+
+ /// Delete the keys created on behalf of the user, denoted by the user id.
+ /// Delete all the keys unless 'keep_non_super_encrypted_keys' set to true.
+ /// Returned boolean is to hint the garbage collector to delete the unbound keys.
+ /// The caller of this function should notify the gc if the returned value is true.
+ pub fn unbind_keys_for_user(
+ &mut self,
+ user_id: u32,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx
+ .prepare(&format!(
+ "SELECT id from persistent.keyentry
+ WHERE (
+ key_type = ?
+ AND domain = ?
+ AND cast ( (namespace/{aid_user_offset}) as int) = ?
+ AND state = ?
+ ) OR (
+ key_type = ?
+ AND namespace = ?
+ AND alias = ?
+ AND state = ?
+ );",
+ aid_user_offset = AID_USER_OFFSET
+ ))
+ .context(concat!(
+ "In unbind_keys_for_user. ",
+ "Failed to prepare the query to find the keys created by apps."
+ ))?;
+
+ let mut rows = stmt
+ .query(params![
+ // WHERE client key:
+ KeyType::Client,
+ Domain::APP.0 as u32,
+ user_id,
+ KeyLifeCycle::Live,
+ // OR super key:
+ KeyType::Super,
+ user_id,
+ Self::USER_SUPER_KEY_ALIAS,
+ KeyLifeCycle::Live
+ ])
+ .context("In unbind_keys_for_user. Failed to query the keys created by apps.")?;
+
+ let mut key_ids: Vec<i64> = Vec::new();
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ key_ids
+ .push(row.get(0).context("Failed to read key id of a key created by an app.")?);
+ Ok(())
+ })
+ .context("In unbind_keys_for_user.")?;
+
+ let mut notify_gc = false;
+ for key_id in key_ids {
+ if keep_non_super_encrypted_keys {
+ // Load metadata and filter out non-super-encrypted keys.
+ if let (_, Some((_, blob_metadata)), _, _) =
+ Self::load_blob_components(key_id, KeyEntryLoadBits::KM, tx)
+ .context("In unbind_keys_for_user: Trying to load blob info.")?
+ {
+ if blob_metadata.encrypted_by().is_none() {
+ continue;
+ }
+ }
+ }
+ notify_gc = Self::mark_unreferenced(&tx, key_id)
+ .context("In unbind_keys_for_user.")?
+ || notify_gc;
+ }
+ Ok(()).do_gc(notify_gc)
+ })
+ .context("In unbind_keys_for_user.")
+ }
+
fn load_key_components(
tx: &Transaction,
load_bits: KeyEntryLoadBits,
@@ -2230,7 +2653,7 @@
) -> Result<KeyEntry> {
let metadata = KeyMetaData::load_from_db(key_id, &tx).context("In load_key_components.")?;
- let (has_km_blob, km_blob, cert_blob, cert_chain_blob) =
+ let (has_km_blob, key_blob_info, cert_blob, cert_chain_blob) =
Self::load_blob_components(key_id, load_bits, &tx)
.context("In load_key_components.")?;
@@ -2242,7 +2665,7 @@
Ok(KeyEntry {
id: key_id,
- km_blob,
+ key_blob_info,
cert: cert_blob,
cert_chain: cert_chain_blob,
km_uuid,
@@ -2279,7 +2702,7 @@
Ok(())
})
.context("In list: Failed to extract rows.")?;
- Ok(descriptors)
+ Ok(descriptors).no_gc()
})
}
@@ -2349,6 +2772,7 @@
};
Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None })
+ .no_gc()
})
}
@@ -2380,7 +2804,7 @@
)
.context("Failed to delete grant.")?;
- Ok(())
+ Ok(()).no_gc()
})
}
@@ -2389,7 +2813,10 @@
// otherwise return the id.
fn insert_with_retry(inserter: impl Fn(i64) -> rusqlite::Result<usize>) -> Result<i64> {
loop {
- let newid: i64 = random();
+ let newid: i64 = match random() {
+ Self::UNASSIGNED_KEY_ID => continue, // UNASSIGNED_KEY_ID cannot be assigned.
+ i => i,
+ };
match inserter(newid) {
// If the id already existed, try again.
Err(rusqlite::Error::SqliteFailure(
@@ -2424,7 +2851,7 @@
],
)
.context("In insert_auth_token: failed to insert auth token into the database")?;
- Ok(())
+ Ok(()).no_gc()
})
}
@@ -2460,10 +2887,11 @@
entry,
Self::get_last_off_body(tx)
.context("In find_auth_token_entry: Trying to get last off body")?,
- )));
+ )))
+ .no_gc();
}
}
- Ok(None)
+ Ok(None).no_gc()
})
.context("In find_auth_token_entry.")
}
@@ -2476,7 +2904,7 @@
params!["last_off_body", last_off_body],
)
.context("In insert_last_off_body: failed to insert.")?;
- Ok(())
+ Ok(()).no_gc()
})
}
@@ -2488,7 +2916,7 @@
params![last_off_body, "last_off_body"],
)
.context("In update_last_off_body: failed to update.")?;
- Ok(())
+ Ok(()).no_gc()
})
}
@@ -2513,6 +2941,7 @@
};
use crate::key_perm_set;
use crate::permission::{KeyPerm, KeyPermSet};
+ use crate::super_key::SuperKeyManager;
use keystore2_test_utils::TempDir;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
@@ -2534,13 +2963,25 @@
fn new_test_db() -> Result<KeystoreDB> {
let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
- let mut db = KeystoreDB { conn };
+ let mut db = KeystoreDB { conn, gc: None };
db.with_transaction(TransactionBehavior::Immediate, |tx| {
- KeystoreDB::init_tables(tx).context("Failed to initialize tables.")
+ KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc()
})?;
Ok(db)
}
+ fn new_test_db_with_gc<F>(path: &Path, cb: F) -> Result<KeystoreDB>
+ where
+ F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static,
+ {
+ let super_key = Arc::new(SuperKeyManager::new());
+
+ let gc_db = KeystoreDB::new(path, None).expect("Failed to open test gc db_connection.");
+ let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db, super_key));
+
+ KeystoreDB::new(path, Some(gc))
+ }
+
fn rebind_alias(
db: &mut KeystoreDB,
newid: &KeyIdGuard,
@@ -2549,7 +2990,7 @@
namespace: i64,
) -> Result<bool> {
db.with_transaction(TransactionBehavior::Immediate, |tx| {
- KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace)
+ KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace).no_gc()
})
.context("In rebind_alias.")
}
@@ -2601,12 +3042,13 @@
.prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
.query_map(params![], |row| row.get(0))?
.collect::<rusqlite::Result<Vec<String>>>()?;
- assert_eq!(tables.len(), 5);
+ assert_eq!(tables.len(), 6);
assert_eq!(tables[0], "blobentry");
- assert_eq!(tables[1], "grant");
- assert_eq!(tables[2], "keyentry");
- assert_eq!(tables[3], "keymetadata");
- assert_eq!(tables[4], "keyparameter");
+ assert_eq!(tables[1], "blobmetadata");
+ assert_eq!(tables[2], "grant");
+ assert_eq!(tables[3], "keyentry");
+ assert_eq!(tables[4], "keymetadata");
+ assert_eq!(tables[5], "keyparameter");
let tables = db
.conn
.prepare("SELECT name from perboot.sqlite_master WHERE type='table' ORDER BY name;")?
@@ -2696,13 +3138,13 @@
#[test]
fn test_persistence_for_files() -> Result<()> {
let temp_dir = TempDir::new("persistent_db_test")?;
- let mut db = KeystoreDB::new(temp_dir.path())?;
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
db.create_key_entry(&Domain::APP, &100, &KEYSTORE_UUID)?;
let entries = get_keyentry(&db)?;
assert_eq!(entries.len(), 1);
- let db = KeystoreDB::new(temp_dir.path())?;
+ let db = KeystoreDB::new(temp_dir.path(), None)?;
let entries_new = get_keyentry(&db)?;
assert_eq!(entries, entries_new);
@@ -2772,8 +3214,9 @@
db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
assert_eq!(true, chain.is_some());
let cert_chain = chain.unwrap();
- assert_eq!(cert_chain.private_key.to_vec(), loaded_values[2]);
- assert_eq!(cert_chain.cert_chain.to_vec(), loaded_values[1]);
+ assert_eq!(cert_chain.private_key.to_vec(), loaded_values.priv_key);
+ assert_eq!(cert_chain.batch_cert, loaded_values.batch_cert);
+ assert_eq!(cert_chain.cert_chain, loaded_values.cert_chain);
Ok(())
}
@@ -2808,6 +3251,7 @@
let private_key: Vec<u8> = vec![0x04, 0x05, 0x06];
let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09];
let cert_chain: Vec<u8> = vec![0x0a, 0x0b, 0x0c];
+ let batch_cert: Vec<u8> = vec![0x0d, 0x0e, 0x0f];
db.create_attestation_key_entry(
&public_key,
&raw_public_key,
@@ -2820,6 +3264,7 @@
assert_eq!(status.total, 4);
db.store_signed_attestation_certificate_chain(
&raw_public_key,
+ &batch_cert,
&cert_chain,
20,
&KEYSTORE_UUID,
@@ -2833,7 +3278,9 @@
#[test]
fn test_remove_expired_certs() -> Result<()> {
- let mut db = new_test_db()?;
+ let temp_dir =
+ TempDir::new("test_remove_expired_certs_").expect("Failed to create temp dir.");
+ let mut db = new_test_db_with_gc(temp_dir.path(), |_, _| Ok(()))?;
let expiration_date: i64 =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 + 10000;
let namespace: i64 = 30;
@@ -2847,40 +3294,49 @@
)?;
load_attestation_key_pool(&mut db, 45, namespace_del1, 0x02)?;
load_attestation_key_pool(&mut db, 60, namespace_del2, 0x03)?;
+
+ let blob_entry_row_count: u32 = db
+ .conn
+ .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0))
+ .expect("Failed to get blob entry row count.");
+ // We expect 9 rows here because there are three blobs per attestation key, i.e.,
+ // one key, one certificate chain, and one certificate.
+ assert_eq!(blob_entry_row_count, 9);
+
assert_eq!(db.delete_expired_attestation_keys()?, 2);
let mut cert_chain =
db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
- assert_eq!(true, cert_chain.is_some());
+ assert!(cert_chain.is_some());
let value = cert_chain.unwrap();
- assert_eq!(entry_values[1], value.cert_chain.to_vec());
- assert_eq!(entry_values[2], value.private_key.to_vec());
+ assert_eq!(entry_values.batch_cert, value.batch_cert);
+ assert_eq!(entry_values.cert_chain, value.cert_chain);
+ assert_eq!(entry_values.priv_key, value.private_key.to_vec());
cert_chain = db.retrieve_attestation_key_and_cert_chain(
Domain::APP,
namespace_del1,
&KEYSTORE_UUID,
)?;
- assert_eq!(false, cert_chain.is_some());
+ assert!(!cert_chain.is_some());
cert_chain = db.retrieve_attestation_key_and_cert_chain(
Domain::APP,
namespace_del2,
&KEYSTORE_UUID,
)?;
- assert_eq!(false, cert_chain.is_some());
+ assert!(!cert_chain.is_some());
- let mut option_entry = db.get_unreferenced_key()?;
- assert_eq!(true, option_entry.is_some());
- let (key_guard, _) = option_entry.unwrap();
- db.purge_key_entry(key_guard)?;
+ // Give the garbage collector half a second to catch up.
+ std::thread::sleep(Duration::from_millis(500));
- option_entry = db.get_unreferenced_key()?;
- assert_eq!(true, option_entry.is_some());
- let (key_guard, _) = option_entry.unwrap();
- db.purge_key_entry(key_guard)?;
+ let blob_entry_row_count: u32 = db
+ .conn
+ .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0))
+ .expect("Failed to get blob entry row count.");
+ // There shound be 3 blob entries left, because we deleted two of the attestation
+ // key entries with three blobs each.
+ assert_eq!(blob_entry_row_count, 3);
- option_entry = db.get_unreferenced_key()?;
- assert_eq!(false, option_entry.is_some());
Ok(())
}
@@ -3120,26 +3576,43 @@
fn test_set_blob() -> Result<()> {
let key_id = KEY_ID_LOCK.get(3000);
let mut db = new_test_db()?;
- db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB))?;
- db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB))?;
- db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB))?;
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+ db.set_blob(
+ &key_id,
+ SubComponentType::KEY_BLOB,
+ Some(TEST_KEY_BLOB),
+ Some(&blob_metadata),
+ )?;
+ db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+ db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
drop(key_id);
let mut stmt = db.conn.prepare(
- "SELECT subcomponent_type, keyentryid, blob FROM persistent.blobentry
+ "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry
ORDER BY subcomponent_type ASC;",
)?;
let mut rows = stmt
- .query_map::<(SubComponentType, i64, Vec<u8>), _, _>(NO_PARAMS, |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
+ .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>(NO_PARAMS, |row| {
+ Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?))
})?;
- let r = rows.next().unwrap().unwrap();
+ let (r, id) = rows.next().unwrap().unwrap();
assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec()));
- let r = rows.next().unwrap().unwrap();
+ let (r, _) = rows.next().unwrap().unwrap();
assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec()));
- let r = rows.next().unwrap().unwrap();
+ let (r, _) = rows.next().unwrap().unwrap();
assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec()));
+ drop(rows);
+ drop(stmt);
+
+ assert_eq!(
+ db.with_transaction(TransactionBehavior::Immediate, |tx| {
+ BlobMetaData::load_from_db(id, tx).no_gc()
+ })
+ .expect("Should find blob metadata."),
+ blob_metadata
+ );
Ok(())
}
@@ -3564,7 +4037,7 @@
let handle = {
let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
let temp_dir_clone = temp_dir.clone();
- let mut db = KeystoreDB::new(temp_dir.path())?;
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None)
.context("test_insert_and_load_full_keyentry_domain_app")?
.0;
@@ -3595,7 +4068,7 @@
// the primary thread.
let handle = thread::spawn(move || {
let temp_dir = temp_dir_clone;
- let mut db = KeystoreDB::new(temp_dir.path()).unwrap();
+ let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
assert!(db
.load_key_entry(
&KeyDescriptor {
@@ -3638,8 +4111,8 @@
let temp_dir =
TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
- let mut db1 = KeystoreDB::new(temp_dir.path()).expect("Failed to open database1.");
- let mut db2 = KeystoreDB::new(temp_dir.path()).expect("Failed to open database2.");
+ let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1.");
+ let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2.");
let _tx1 = db1
.conn
@@ -3799,7 +4272,7 @@
#[test]
fn list() -> Result<()> {
let temp_dir = TempDir::new("list_test")?;
- let mut db = KeystoreDB::new(temp_dir.path())?;
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
(Domain::APP, 1, "test1"),
(Domain::APP, 1, "test2"),
@@ -3935,30 +4408,33 @@
.collect::<Result<Vec<_>>>()
}
+ struct RemoteProvValues {
+ cert_chain: Vec<u8>,
+ priv_key: Vec<u8>,
+ batch_cert: Vec<u8>,
+ }
+
fn load_attestation_key_pool(
db: &mut KeystoreDB,
expiration_date: i64,
namespace: i64,
base_byte: u8,
- ) -> Result<Vec<Vec<u8>>> {
- let mut chain: Vec<Vec<u8>> = Vec::new();
+ ) -> Result<RemoteProvValues> {
let public_key: Vec<u8> = vec![base_byte, 0x02 * base_byte];
let cert_chain: Vec<u8> = vec![0x03 * base_byte, 0x04 * base_byte];
let priv_key: Vec<u8> = vec![0x05 * base_byte, 0x06 * base_byte];
let raw_public_key: Vec<u8> = vec![0x0b * base_byte, 0x0c * base_byte];
+ let batch_cert: Vec<u8> = vec![base_byte * 0x0d, base_byte * 0x0e];
db.create_attestation_key_entry(&public_key, &raw_public_key, &priv_key, &KEYSTORE_UUID)?;
db.store_signed_attestation_certificate_chain(
&raw_public_key,
+ &batch_cert,
&cert_chain,
expiration_date,
&KEYSTORE_UUID,
)?;
db.assign_attestation_key(Domain::APP, namespace, &KEYSTORE_UUID)?;
- chain.push(public_key);
- chain.push(cert_chain);
- chain.push(priv_key);
- chain.push(raw_public_key);
- Ok(chain)
+ Ok(RemoteProvValues { cert_chain, priv_key, batch_cert })
}
// Note: The parameters and SecurityLevel associations are nonsensical. This
@@ -4198,18 +4674,27 @@
max_usage_count: Option<i32>,
) -> Result<KeyIdGuard> {
let key_id = db.create_key_entry(&domain, &namespace, &KEYSTORE_UUID)?;
- db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB))?;
- db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB))?;
- db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB))?;
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+ blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+ blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+ db.set_blob(
+ &key_id,
+ SubComponentType::KEY_BLOB,
+ Some(TEST_KEY_BLOB),
+ Some(&blob_metadata),
+ )?;
+ db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+ db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
let params = make_test_params(max_usage_count);
db.insert_keyparameter(&key_id, ¶ms)?;
let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::EncryptedBy(EncryptedBy::Password));
- metadata.add(KeyMetaEntry::Salt(vec![1, 2, 3]));
- metadata.add(KeyMetaEntry::Iv(vec![2, 3, 1]));
- metadata.add(KeyMetaEntry::AeadTag(vec![3, 1, 2]));
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
db.insert_key_metadata(&key_id, &metadata)?;
rebind_alias(db, &key_id, alias, domain, namespace)?;
Ok(key_id)
@@ -4218,15 +4703,19 @@
fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry {
let params = make_test_params(max_usage_count);
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+ blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+ blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::EncryptedBy(EncryptedBy::Password));
- metadata.add(KeyMetaEntry::Salt(vec![1, 2, 3]));
- metadata.add(KeyMetaEntry::Iv(vec![2, 3, 1]));
- metadata.add(KeyMetaEntry::AeadTag(vec![3, 1, 2]));
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
KeyEntry {
id: key_id,
- km_blob: Some(TEST_KEY_BLOB.to_vec()),
+ key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
cert: Some(TEST_CERT_BLOB.to_vec()),
cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
km_uuid: KEYSTORE_UUID,
@@ -4319,4 +4808,53 @@
assert!(last_off_body_1.seconds() < last_off_body_2.seconds());
Ok(())
}
+
+ #[test]
+ fn test_unbind_keys_for_user() -> Result<()> {
+ let mut db = new_test_db()?;
+ db.unbind_keys_for_user(1, false)?;
+
+ make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
+ make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
+ db.unbind_keys_for_user(2, false)?;
+
+ assert_eq!(1, db.list(Domain::APP, 110000)?.len());
+ assert_eq!(0, db.list(Domain::APP, 210000)?.len());
+
+ db.unbind_keys_for_user(1, true)?;
+ assert_eq!(0, db.list(Domain::APP, 110000)?.len());
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_store_super_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let pw = "xyzabc".as_bytes();
+ let super_key = keystore2_crypto::generate_aes256_key()?;
+ let secret = String::from("keystore2 is great.");
+ let secret_bytes = secret.into_bytes();
+ let (encrypted_secret, iv, tag) =
+ keystore2_crypto::aes_gcm_encrypt(&secret_bytes, &super_key)?;
+
+ let (encrypted_super_key, metadata) =
+ SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+ db.store_super_key(1, &(&encrypted_super_key, &metadata))?;
+
+ //check if super key exists
+ assert!(db.key_exists(Domain::APP, 1, "USER_SUPER_KEY", KeyType::Super)?);
+
+ let (_, key_entry) = db.load_super_key(1)?.unwrap();
+ let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(key_entry, &pw)?;
+
+ let decrypted_secret_bytes = keystore2_crypto::aes_gcm_decrypt(
+ &encrypted_secret,
+ &iv,
+ &tag,
+ &loaded_super_key.get_key(),
+ )?;
+ let decrypted_secret = String::from_utf8((&decrypted_secret_bytes).to_vec())?;
+ assert_eq!(String::from("keystore2 is great."), decrypted_secret);
+ Ok(())
+ }
}
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 13068c5..aa852f4 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -14,13 +14,10 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
+use crate::database::{AuthTokenEntry, MonotonicRawTime};
use crate::error::{map_binder_status, Error, ErrorCode};
use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS};
use crate::key_parameter::{KeyParameter, KeyParameterValue};
-use crate::{
- database::{AuthTokenEntry, MonotonicRawTime},
- gc::Gc,
-};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, ErrorCode::ErrorCode as Ec, HardwareAuthToken::HardwareAuthToken,
HardwareAuthenticatorType::HardwareAuthenticatorType,
@@ -29,17 +26,21 @@
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
};
-use android_system_keystore2::aidl::android::system::keystore2::OperationChallenge::OperationChallenge;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
+ OperationChallenge::OperationChallenge,
+};
use android_system_keystore2::binder::Strong;
use anyhow::{Context, Result};
-use std::sync::{
- mpsc::{channel, Receiver, Sender},
- Arc, Mutex, Weak,
-};
-use std::time::SystemTime;
+use keystore2_system_property::PropertyWatcher;
use std::{
collections::{HashMap, HashSet},
- sync::mpsc::TryRecvError,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ mpsc::{channel, Receiver, Sender, TryRecvError},
+ Arc, Mutex, Weak,
+ },
+ time::SystemTime,
};
#[derive(Debug)]
@@ -245,7 +246,7 @@
let token_receiver = TokenReceiver(Arc::downgrade(&auth_request));
ENFORCEMENTS.register_op_auth_receiver(challenge, token_receiver);
- ASYNC_TASK.queue_hi(move || timestamp_token_request(challenge, sender));
+ ASYNC_TASK.queue_hi(move |_| timestamp_token_request(challenge, sender));
self.state = DeferredAuthState::Waiting(auth_request);
Some(OperationChallenge { challenge })
}
@@ -253,7 +254,7 @@
let hat = (*hat).clone();
let (sender, receiver) = channel::<Result<TimeStampToken, Error>>();
let auth_request = AuthRequest::timestamp(hat, receiver);
- ASYNC_TASK.queue_hi(move || timestamp_token_request(challenge, sender));
+ ASYNC_TASK.queue_hi(move |_| timestamp_token_request(challenge, sender));
self.state = DeferredAuthState::Waiting(auth_request);
None
}
@@ -305,16 +306,12 @@
if let Some(key_id) = self.key_usage_limited {
// On the last successful use, the key gets deleted. In this case we
// have to notify the garbage collector.
- let need_gc = DB
- .with(|db| {
- db.borrow_mut()
- .check_and_update_key_usage_count(key_id)
- .context("Trying to update key usage count.")
- })
- .context("In after_finish.")?;
- if need_gc {
- Gc::notify_gc();
- }
+ DB.with(|db| {
+ db.borrow_mut()
+ .check_and_update_key_usage_count(key_id)
+ .context("Trying to update key usage count.")
+ })
+ .context("In after_finish.")?;
}
Ok(())
}
@@ -356,6 +353,7 @@
}
/// Enforcements data structure
+#[derive(Default)]
pub struct Enforcements {
/// This hash set contains the user ids for whom the device is currently unlocked. If a user id
/// is not in the set, it implies that the device is locked for the user.
@@ -369,18 +367,11 @@
/// The enforcement module will try to get a confirmation token from this channel whenever
/// an operation that requires confirmation finishes.
confirmation_token_receiver: Arc<Mutex<Option<Receiver<Vec<u8>>>>>,
+ /// Highest boot level seen in keystore.boot_level; used to enforce MAX_BOOT_LEVEL tag.
+ boot_level: AtomicI32,
}
impl Enforcements {
- /// Creates an enforcement object with the two data structures it holds and the sender as None.
- pub fn new() -> Self {
- Enforcements {
- device_unlocked_set: Mutex::new(HashSet::new()),
- op_auth_map: Default::default(),
- confirmation_token_receiver: Default::default(),
- }
- }
-
/// Install the confirmation token receiver. The enforcement module will try to get a
/// confirmation token from this channel whenever an operation that requires confirmation
/// finishes.
@@ -479,6 +470,7 @@
let mut unlocked_device_required = false;
let mut key_usage_limited: Option<i64> = None;
let mut confirmation_token_receiver: Option<Arc<Mutex<Option<Receiver<Vec<u8>>>>>> = None;
+ let mut max_boot_level: Option<i32> = None;
// iterate through key parameters, recording information we need for authorization
// enforcements later, or enforcing authorizations in place, where applicable
@@ -545,6 +537,9 @@
KeyParameterValue::TrustedConfirmationRequired => {
confirmation_token_receiver = Some(self.confirmation_token_receiver.clone());
}
+ KeyParameterValue::MaxBootLevel(level) => {
+ max_boot_level = Some(*level);
+ }
// NOTE: as per offline discussion, sanitizing key parameters and rejecting
// create operation if any non-allowed tags are present, is not done in
// authorize_create (unlike in legacy keystore where AuthorizeBegin is rejected if
@@ -598,6 +593,13 @@
}
}
+ if let Some(level) = max_boot_level {
+ if level < self.boot_level.load(Ordering::SeqCst) {
+ return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED))
+ .context("In authorize_create: boot level is too late.");
+ }
+ }
+
if !unlocked_device_required && no_auth_required {
return Ok((
None,
@@ -751,11 +753,47 @@
fn register_op_auth_receiver(&self, challenge: i64, recv: TokenReceiver) {
self.op_auth_map.add_receiver(challenge, recv);
}
-}
-impl Default for Enforcements {
- fn default() -> Self {
- Self::new()
+ /// Given the set of key parameters and flags, check if super encryption is required.
+ pub fn super_encryption_required(key_parameters: &[KeyParameter], flags: Option<i32>) -> bool {
+ let auth_bound = key_parameters.iter().any(|kp| kp.get_tag() == Tag::USER_SECURE_ID);
+
+ let skip_lskf_binding = if let Some(flags) = flags {
+ (flags & KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING) != 0
+ } else {
+ false
+ };
+
+ auth_bound && !skip_lskf_binding
+ }
+
+ /// Watch the `keystore.boot_level` system property, and keep self.boot_level up to date.
+ /// Blocks waiting for system property changes, so must be run in its own thread.
+ pub fn watch_boot_level(&self) -> Result<()> {
+ let mut w = PropertyWatcher::new("keystore.boot_level")?;
+ loop {
+ fn parse_value(_name: &str, value: &str) -> Result<Option<i32>> {
+ Ok(if value == "end" { None } else { Some(value.parse::<i32>()?) })
+ }
+ match w.read(parse_value)? {
+ Some(level) => {
+ let old = self.boot_level.fetch_max(level, Ordering::SeqCst);
+ log::info!(
+ "Read keystore.boot_level: {}; boot level {} -> {}",
+ level,
+ old,
+ std::cmp::max(old, level)
+ );
+ }
+ None => {
+ log::info!("keystore.boot_level is `end`, finishing.");
+ self.boot_level.fetch_max(i32::MAX, Ordering::SeqCst);
+ break;
+ }
+ }
+ w.wait()?;
+ }
+ Ok(())
}
}
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index 7227f62..388487c 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -57,6 +57,10 @@
/// Wraps a Binder status code.
#[error("Binder transaction error {0:?}")]
BinderTransaction(StatusCode),
+ /// Wraps a Remote Provisioning ErrorCode as defined by the IRemotelyProvisionedComponent
+ /// AIDL interface spec.
+ #[error("Error::Rp({0:?})")]
+ Rp(ErrorCode),
}
impl Error {
@@ -101,6 +105,16 @@
})
}
+/// Helper function to map the binder status we get from calls into a RemotelyProvisionedComponent
+/// to a Keystore Error. We don't create an anyhow error here to make
+/// it easier to evaluate service specific errors.
+pub fn map_rem_prov_error<T>(r: BinderResult<T>) -> Result<T, Error> {
+ r.map_err(|s| match s.exception_code() {
+ ExceptionCode::SERVICE_SPECIFIC => Error::Rp(ErrorCode(s.service_specific_error())),
+ e_code => Error::Binder(e_code, 0),
+ })
+}
+
/// This function is similar to map_km_error only that we don't expect
/// any KeyMint error codes, we simply preserve the exception code and optional
/// service specific exception.
@@ -157,13 +171,36 @@
where
F: FnOnce(U) -> BinderResult<T>,
{
- result.map_or_else(
+ map_err_with(
+ result,
|e| {
log::error!("{:?}", e);
+ e
+ },
+ handle_ok,
+ )
+}
+
+/// This function behaves similar to map_or_log_error, but it does not log the errors, instead
+/// it calls map_err on the error before mapping it to a binder result allowing callers to
+/// log or transform the error before mapping it.
+pub fn map_err_with<T, U, F1, F2>(
+ result: anyhow::Result<U>,
+ map_err: F1,
+ handle_ok: F2,
+) -> BinderResult<T>
+where
+ F1: FnOnce(anyhow::Error) -> anyhow::Error,
+ F2: FnOnce(U) -> BinderResult<T>,
+{
+ result.map_or_else(
+ |e| {
+ let e = map_err(e);
let root_cause = e.root_cause();
let rc = match root_cause.downcast_ref::<Error>() {
Some(Error::Rc(rcode)) => rcode.0,
Some(Error::Km(ec)) => ec.0,
+ Some(Error::Rp(_)) => ResponseCode::SYSTEM_ERROR.0,
// If an Error::Binder reaches this stage we report a system error.
// The exception code and possible service specific error will be
// printed in the error log above.
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index fbb1cf6..6cc0f27 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -18,80 +18,106 @@
//! optionally dispose of sensitive key material appropriately, and then delete
//! the key entry from the database.
-use crate::globals::{get_keymint_dev_by_uuid, DB};
-use crate::{error::map_km_error, globals::ASYNC_TASK};
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
-use android_hardware_security_keymint::binder::Strong;
-use anyhow::Result;
+use crate::{
+ async_task,
+ database::{KeystoreDB, Uuid},
+ super_key::SuperKeyManager,
+};
+use anyhow::{Context, Result};
+use async_task::AsyncTask;
+use std::sync::Arc;
-#[derive(Clone, Copy)]
pub struct Gc {
- remaining_tries: u32,
+ async_task: Arc<AsyncTask>,
}
impl Gc {
- const MAX_ERROR_RETRIES: u32 = 3u32;
+ /// Creates a garbage collector using the given async_task.
+ /// The garbage collector needs a function to invalidate key blobs and a database connection.
+ /// Both are obtained from the init function. The function is only called if this is first
+ /// time a garbage collector was initialized with the given AsyncTask instance.
+ pub fn new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self
+ where
+ F: FnOnce() -> (
+ Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
+ KeystoreDB,
+ Arc<SuperKeyManager>,
+ ) + Send
+ + 'static,
+ {
+ let weak_at = Arc::downgrade(&async_task);
+ // Initialize the task's shelf.
+ async_task.queue_hi(move |shelf| {
+ let (invalidate_key, db, super_key) = init();
+ shelf.get_or_put_with(|| GcInternal {
+ blob_id_to_delete: None,
+ invalidate_key,
+ db,
+ async_task: weak_at,
+ super_key,
+ });
+ });
+ Self { async_task }
+ }
- /// Attempts to process one unreferenced key from the database.
- /// Returns Ok(true) if a key was deleted and Ok(false) if there were no more keys to process.
+ /// Notifies the key garbage collector to iterate through orphaned and superseded blobs and
+ /// attempts their deletion. We only process one key at a time and then schedule another
+ /// attempt by queueing it in the async_task (low priority) queue.
+ pub fn notify_gc(&self) {
+ self.async_task.queue_lo(|shelf| shelf.get_downcast_mut::<GcInternal>().unwrap().step())
+ }
+}
+
+struct GcInternal {
+ blob_id_to_delete: Option<i64>,
+ invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
+ db: KeystoreDB,
+ async_task: std::sync::Weak<AsyncTask>,
+ super_key: Arc<SuperKeyManager>,
+}
+
+impl GcInternal {
+ /// Attempts to process one blob from the database.
/// We process one key at a time, because deleting a key is a time consuming process which
/// may involve calling into the KeyMint backend and we don't want to hog neither the backend
/// nor the database for extended periods of time.
- fn process_one_key() -> Result<bool> {
- DB.with(|db| {
- let mut db = db.borrow_mut();
- if let Some((key_id, mut key_entry)) = db.get_unreferenced_key()? {
- if let Some(blob) = key_entry.take_km_blob() {
- let km_dev: Strong<dyn IKeyMintDevice> =
- get_keymint_dev_by_uuid(key_entry.km_uuid())
- .map(|(dev, _)| dev)?
- .get_interface()?;
- if let Err(e) = map_km_error(km_dev.deleteKey(&blob)) {
- // Log but ignore error.
- log::error!("Error trying to delete key. {:?}", e);
- }
- }
- db.purge_key_entry(key_id)?;
- return Ok(true);
- }
- Ok(false)
- })
- }
+ fn process_one_key(&mut self) -> Result<()> {
+ if let Some((blob_id, blob, blob_metadata)) = self
+ .db
+ .handle_next_superseded_blob(self.blob_id_to_delete.take())
+ .context("In process_one_key: Trying to handle superseded blob.")?
+ {
+ // Set the blob_id as the next to be deleted blob. So it will be
+ // removed from the database regardless of whether the following
+ // succeeds or not.
+ self.blob_id_to_delete = Some(blob_id);
- /// Processes one key and then schedules another attempt until it runs out of tries or keys
- /// to delete.
- fn process_all(mut self) {
- match Self::process_one_key() {
- // We successfully removed a key.
- Ok(true) => self.remaining_tries = Self::MAX_ERROR_RETRIES,
- // There were no more keys to remove. We may exit.
- Ok(false) => self.remaining_tries = 0,
- // An error occurred. We retry in case the error was transient, but
- // we also count down the number of tries so that we don't spin
- // indefinitely.
- Err(e) => {
- self.remaining_tries -= 1;
- log::error!(
- concat!(
- "Failed to delete key. Retrying in case this error was transient. ",
- "(Tries remaining {}) {:?}"
- ),
- self.remaining_tries,
- e
- )
+ // If the key has a km_uuid we try to get the corresponding device
+ // and delete the key, unwrapping if necessary and possible.
+ // (At this time keys may get deleted without having the super encryption
+ // key in this case we can only delete the key from the database.)
+ if let Some(uuid) = blob_metadata.km_uuid() {
+ let blob = self
+ .super_key
+ .unwrap_key_if_required(&blob_metadata, &blob)
+ .context("In process_one_key: Trying to unwrap to-be-deleted blob.")?;
+ (self.invalidate_key)(&uuid, &*blob)
+ .context("In process_one_key: Trying to invalidate key.")?;
}
}
- if self.remaining_tries != 0 {
- ASYNC_TASK.queue_lo(move || {
- self.process_all();
- })
- }
+ Ok(())
}
- /// Notifies the key garbage collector to iterate through unreferenced keys and attempt
- /// their deletion. We only process one key at a time and then schedule another
- /// attempt by queueing it in the async_task (low priority) queue.
- pub fn notify_gc() {
- ASYNC_TASK.queue_lo(|| Self { remaining_tries: Self::MAX_ERROR_RETRIES }.process_all())
+ /// Processes one key and then schedules another attempt until it runs out of blobs to delete.
+ fn step(&mut self) {
+ if let Err(e) = self.process_one_key() {
+ log::error!("Error trying to delete blob entry. {:?}", e);
+ }
+ // Schedule the next step. This gives high priority requests a chance to interleave.
+ if self.blob_id_to_delete.is_some() {
+ if let Some(at) = self.async_task.upgrade() {
+ at.queue_lo(move |shelf| shelf.get_downcast_mut::<GcInternal>().unwrap().step());
+ }
+ }
}
}
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 2afabbe..04bfbc9 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -18,6 +18,7 @@
use crate::gc::Gc;
use crate::legacy_blob::LegacyBlobLoader;
+use crate::legacy_migrator::LegacyMigrator;
use crate::super_key::SuperKeyManager;
use crate::utils::Asp;
use crate::{async_task::AsyncTask, database::MonotonicRawTime};
@@ -28,13 +29,15 @@
};
use crate::{enforcements::Enforcements, error::map_km_error};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ IKeyMintDevice::IKeyMintDevice, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent,
KeyMintHardwareInfo::KeyMintHardwareInfo, SecurityLevel::SecurityLevel,
};
use android_hardware_security_keymint::binder::{StatusCode, Strong};
use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
use anyhow::{Context, Result};
+use keystore2_vintf::get_aidl_instances;
use lazy_static::lazy_static;
-use std::sync::Mutex;
+use std::sync::{Arc, Mutex};
use std::{cell::RefCell, sync::Once};
use std::{collections::HashMap, path::Path, path::PathBuf};
@@ -43,10 +46,29 @@
/// Open a connection to the Keystore 2.0 database. This is called during the initialization of
/// the thread local DB field. It should never be called directly. The first time this is called
/// we also call KeystoreDB::cleanup_leftovers to restore the key lifecycle invariant. See the
-/// documentation of cleanup_leftovers for more details.
-fn create_thread_local_db() -> KeystoreDB {
- let mut db = KeystoreDB::new(&DB_PATH.lock().expect("Could not get the database directory."))
- .expect("Failed to open database.");
+/// documentation of cleanup_leftovers for more details. The function also constructs a blob
+/// garbage collector. The initializing closure constructs another database connection without
+/// a gc. Although one GC is created for each thread local database connection, this closure
+/// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional
+/// database connection is created for the garbage collector worker.
+pub fn create_thread_local_db() -> KeystoreDB {
+ let gc = Gc::new_init_with(ASYNC_TASK.clone(), || {
+ (
+ Box::new(|uuid, blob| {
+ let km_dev: Strong<dyn IKeyMintDevice> =
+ get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?.get_interface()?;
+ map_km_error(km_dev.deleteKey(&*blob))
+ .context("In invalidate key closure: Trying to invalidate key blob.")
+ }),
+ KeystoreDB::new(&DB_PATH.lock().expect("Could not get the database directory."), None)
+ .expect("Failed to open database."),
+ SUPER_KEY.clone(),
+ )
+ });
+
+ let mut db =
+ KeystoreDB::new(&DB_PATH.lock().expect("Could not get the database directory."), Some(gc))
+ .expect("Failed to open database.");
DB_INIT.call_once(|| {
log::info!("Touching Keystore 2.0 database for this first time since boot.");
db.insert_last_off_body(MonotonicRawTime::now())
@@ -62,7 +84,6 @@
n
);
}
- Gc::notify_gc();
});
db
}
@@ -108,25 +129,45 @@
}
}
+#[derive(Default)]
+struct RemotelyProvisionedDevicesMap {
+ devices_by_sec_level: HashMap<SecurityLevel, Asp>,
+}
+
+impl RemotelyProvisionedDevicesMap {
+ fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Asp> {
+ self.devices_by_sec_level.get(sec_level).map(|dev| (*dev).clone())
+ }
+
+ fn insert(&mut self, sec_level: SecurityLevel, dev: Asp) {
+ self.devices_by_sec_level.insert(sec_level, dev);
+ }
+}
+
lazy_static! {
/// The path where keystore stores all its keys.
pub static ref DB_PATH: Mutex<PathBuf> = Mutex::new(
Path::new("/data/misc/keystore").to_path_buf());
/// Runtime database of unwrapped super keys.
- pub static ref SUPER_KEY: SuperKeyManager = Default::default();
+ pub static ref SUPER_KEY: Arc<SuperKeyManager> = Default::default();
/// Map of KeyMint devices.
static ref KEY_MINT_DEVICES: Mutex<DevicesMap> = Default::default();
/// Timestamp service.
static ref TIME_STAMP_DEVICE: Mutex<Option<Asp>> = Default::default();
+ /// RemotelyProvisionedComponent HAL devices.
+ static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: Mutex<RemotelyProvisionedDevicesMap> = Default::default();
/// A single on-demand worker thread that handles deferred tasks with two different
/// priorities.
- pub static ref ASYNC_TASK: AsyncTask = Default::default();
+ pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default();
/// Singleton for enforcements.
- pub static ref ENFORCEMENTS: Enforcements = Enforcements::new();
+ pub static ref ENFORCEMENTS: Enforcements = Default::default();
/// LegacyBlobLoader is initialized and exists globally.
/// The same directory used by the database is used by the LegacyBlobLoader as well.
- pub static ref LEGACY_BLOB_LOADER: LegacyBlobLoader = LegacyBlobLoader::new(
- &DB_PATH.lock().expect("Could not get the database path for legacy blob loader."));
+ pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new(
+ &DB_PATH.lock().expect("Could not get the database path for legacy blob loader.")));
+ /// Legacy migrator. Atomically migrates legacy blobs to the database.
+ pub static ref LEGACY_MIGRATOR: Arc<LegacyMigrator> =
+ Arc::new(LegacyMigrator::new(ASYNC_TASK.clone()));
}
static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice";
@@ -135,38 +176,49 @@
/// If no native KeyMint device can be found this function also brings
/// up the compatibility service and attempts to connect to the legacy wrapper.
fn connect_keymint(security_level: &SecurityLevel) -> Result<(Asp, KeyMintHardwareInfo)> {
+ let keymint_instances =
+ get_aidl_instances("android.hardware.security.keymint", 1, "IKeyMintDevice");
+
let service_name = match *security_level {
- SecurityLevel::TRUSTED_ENVIRONMENT => format!("{}/default", KEYMINT_SERVICE_NAME),
- SecurityLevel::STRONGBOX => format!("{}/strongbox", KEYMINT_SERVICE_NAME),
+ SecurityLevel::TRUSTED_ENVIRONMENT => {
+ if keymint_instances.as_vec()?.iter().any(|instance| *instance == "default") {
+ Some(format!("{}/default", KEYMINT_SERVICE_NAME))
+ } else {
+ None
+ }
+ }
+ SecurityLevel::STRONGBOX => {
+ if keymint_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") {
+ Some(format!("{}/strongbox", KEYMINT_SERVICE_NAME))
+ } else {
+ None
+ }
+ }
_ => {
return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
.context("In connect_keymint.")
}
};
- let keymint = map_binder_status_code(binder::get_interface(&service_name))
- .context("In connect_keymint: Trying to connect to genuine KeyMint service.")
- .or_else(|e| {
- match e.root_cause().downcast_ref::<Error>() {
- Some(Error::BinderTransaction(StatusCode::NAME_NOT_FOUND)) => {
- // This is a no-op if it was called before.
- keystore2_km_compat::add_keymint_device_service();
+ let keymint = if let Some(service_name) = service_name {
+ map_binder_status_code(binder::get_interface(&service_name))
+ .context("In connect_keymint: Trying to connect to genuine KeyMint service.")
+ } else {
+ // This is a no-op if it was called before.
+ keystore2_km_compat::add_keymint_device_service();
- let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
- map_binder_status_code(binder::get_interface("android.security.compat"))
- .context("In connect_keymint: Trying to connect to compat service.")?;
- map_binder_status(keystore_compat_service.getKeyMintDevice(*security_level))
- .map_err(|e| match e {
- Error::BinderTransaction(StatusCode::NAME_NOT_FOUND) => {
- Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)
- }
- e => e,
- })
- .context("In connect_keymint: Trying to get Legacy wrapper.")
+ let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
+ map_binder_status_code(binder::get_interface("android.security.compat"))
+ .context("In connect_keymint: Trying to connect to compat service.")?;
+ map_binder_status(keystore_compat_service.getKeyMintDevice(*security_level))
+ .map_err(|e| match e {
+ Error::BinderTransaction(StatusCode::NAME_NOT_FOUND) => {
+ Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)
}
- _ => Err(e),
- }
- })?;
+ e => e,
+ })
+ .context("In connect_keymint: Trying to get Legacy wrapper.")
+ }?;
let hw_info = map_km_error(keymint.getHardwareInfo())
.context("In connect_keymint: Failed to get hardware info.")?;
@@ -210,33 +262,33 @@
/// If no native SecureClock device can be found brings up the compatibility service and attempts
/// to connect to the legacy wrapper.
fn connect_secureclock() -> Result<Asp> {
- let secureclock = map_binder_status_code(binder::get_interface(TIME_STAMP_SERVICE_NAME))
- .context("In connect_secureclock: Trying to connect to genuine secure clock service.")
- .or_else(|e| {
- match e.root_cause().downcast_ref::<Error>() {
- Some(Error::BinderTransaction(StatusCode::NAME_NOT_FOUND)) => {
- // This is a no-op if it was called before.
- keystore2_km_compat::add_keymint_device_service();
+ let secureclock_instances =
+ get_aidl_instances("android.hardware.security.secureclock", 1, "ISecureClock");
- let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
- map_binder_status_code(binder::get_interface("android.security.compat"))
- .context(
- "In connect_secureclock: Trying to connect to compat service.",
- )?;
+ let secure_clock_available =
+ secureclock_instances.as_vec()?.iter().any(|instance| *instance == "default");
- // Legacy secure clock services were only implemented by TEE.
- map_binder_status(keystore_compat_service.getSecureClock())
- .map_err(|e| match e {
- Error::BinderTransaction(StatusCode::NAME_NOT_FOUND) => {
- Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)
- }
- e => e,
- })
- .context("In connect_secureclock: Trying to get Legacy wrapper.")
+ let secureclock = if secure_clock_available {
+ map_binder_status_code(binder::get_interface(TIME_STAMP_SERVICE_NAME))
+ .context("In connect_secureclock: Trying to connect to genuine secure clock service.")
+ } else {
+ // This is a no-op if it was called before.
+ keystore2_km_compat::add_keymint_device_service();
+
+ let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
+ map_binder_status_code(binder::get_interface("android.security.compat"))
+ .context("In connect_secureclock: Trying to connect to compat service.")?;
+
+ // Legacy secure clock services were only implemented by TEE.
+ map_binder_status(keystore_compat_service.getSecureClock())
+ .map_err(|e| match e {
+ Error::BinderTransaction(StatusCode::NAME_NOT_FOUND) => {
+ Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)
}
- _ => Err(e),
- }
- })?;
+ e => e,
+ })
+ .context("In connect_secureclock: Trying to get Legacy wrapper.")
+ }?;
Ok(Asp::new(secureclock.as_binder()))
}
@@ -253,3 +305,55 @@
Ok(dev)
}
}
+
+static REMOTE_PROVISIONING_HAL_SERVICE_NAME: &str =
+ "android.hardware.security.keymint.IRemotelyProvisionedComponent";
+
+fn connect_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> {
+ let remotely_prov_instances =
+ get_aidl_instances("android.hardware.security.keymint", 1, "IRemotelyProvisionedComponent");
+
+ let service_name = match *security_level {
+ SecurityLevel::TRUSTED_ENVIRONMENT => {
+ if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "default") {
+ Some(format!("{}/default", REMOTE_PROVISIONING_HAL_SERVICE_NAME))
+ } else {
+ None
+ }
+ }
+ SecurityLevel::STRONGBOX => {
+ if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") {
+ Some(format!("{}/strongbox", REMOTE_PROVISIONING_HAL_SERVICE_NAME))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ .ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
+ .context("In connect_remotely_provisioned_component.")?;
+
+ let rem_prov_hal: Strong<dyn IRemotelyProvisionedComponent> =
+ map_binder_status_code(binder::get_interface(&service_name))
+ .context(concat!(
+ "In connect_remotely_provisioned_component: Trying to connect to",
+ " RemotelyProvisionedComponent service."
+ ))
+ .map_err(|e| e)?;
+ Ok(Asp::new(rem_prov_hal.as_binder()))
+}
+
+/// Get a remote provisiong component device for the given security level either from the cache or
+/// by making a new connection. Returns the device.
+pub fn get_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> {
+ let mut devices_map = REMOTELY_PROVISIONED_COMPONENT_DEVICES.lock().unwrap();
+ if let Some(dev) = devices_map.dev_by_sec_level(&security_level) {
+ Ok(dev)
+ } else {
+ let dev = connect_remotely_provisioned_component(security_level)
+ .context("In get_remotely_provisioned_component.")?;
+ devices_map.insert(*security_level, dev);
+ // Unwrap must succeed because we just inserted it.
+ Ok(devices_map.dev_by_sec_level(security_level).unwrap())
+ }
+}
diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs
index 117dea8..c10da95 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -965,6 +965,9 @@
/// Used to deliver the not after date in milliseconds to KeyMint during key generation/import.
#[key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
CertificateNotAfter(i64),
+ /// Specifies a maximum boot level at which a key should function
+ #[key_param(tag = MAX_BOOT_LEVEL, field = Integer)]
+ MaxBootLevel(i32),
}
}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 75475e1..7739f5e 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -17,13 +17,19 @@
use keystore2::apc::ApcManager;
use keystore2::authorization::AuthorizationManager;
use keystore2::globals::ENFORCEMENTS;
+use keystore2::remote_provisioning::RemoteProvisioningService;
use keystore2::service::KeystoreService;
+use keystore2::user_manager::UserManager;
use log::{error, info};
use std::{panic, path::Path, sync::mpsc::channel};
+use vpnprofilestore::VpnProfileStore;
static KS2_SERVICE_NAME: &str = "android.system.keystore2";
static APC_SERVICE_NAME: &str = "android.security.apc";
static AUTHORIZATION_SERVICE_NAME: &str = "android.security.authorization";
+static REMOTE_PROVISIONING_SERVICE_NAME: &str = "android.security.remoteprovisioning";
+static USER_MANAGER_SERVICE_NAME: &str = "android.security.usermanager";
+static VPNPROFILESTORE_SERVICE_NAME: &str = "android.security.vpnprofilestore";
/// Keystore 2.0 takes one argument which is a path indicating its designated working directory.
fn main() {
@@ -61,6 +67,13 @@
ENFORCEMENTS.install_confirmation_token_receiver(confirmation_token_receiver);
+ info!("Starting boot level watcher.");
+ std::thread::spawn(|| {
+ keystore2::globals::ENFORCEMENTS
+ .watch_boot_level()
+ .unwrap_or_else(|e| error!("watch_boot_level failed: {}", e));
+ });
+
info!("Starting thread pool now.");
binder::ProcessState::start_thread_pool();
@@ -87,6 +100,42 @@
panic!("Failed to register service {} because of {:?}.", AUTHORIZATION_SERVICE_NAME, e);
});
+ let usermanager_service = UserManager::new_native_binder().unwrap_or_else(|e| {
+ panic!("Failed to create service {} because of {:?}.", USER_MANAGER_SERVICE_NAME, e);
+ });
+ binder::add_service(USER_MANAGER_SERVICE_NAME, usermanager_service.as_binder()).unwrap_or_else(
+ |e| {
+ panic!("Failed to register service {} because of {:?}.", USER_MANAGER_SERVICE_NAME, e);
+ },
+ );
+
+ // Devices with KS2 and KM 1.0 may not have any IRemotelyProvisionedComponent HALs at all. Do
+ // not panic if new_native_binder returns failure because it could not find the TEE HAL.
+ if let Ok(remote_provisioning_service) = RemoteProvisioningService::new_native_binder() {
+ binder::add_service(
+ REMOTE_PROVISIONING_SERVICE_NAME,
+ remote_provisioning_service.as_binder(),
+ )
+ .unwrap_or_else(|e| {
+ panic!(
+ "Failed to register service {} because of {:?}.",
+ REMOTE_PROVISIONING_SERVICE_NAME, e
+ );
+ });
+ }
+
+ let vpnprofilestore = VpnProfileStore::new_native_binder(
+ &keystore2::globals::DB_PATH.lock().expect("Could not get DB_PATH."),
+ );
+ binder::add_service(VPNPROFILESTORE_SERVICE_NAME, vpnprofilestore.as_binder()).unwrap_or_else(
+ |e| {
+ panic!(
+ "Failed to register service {} because of {:?}.",
+ VPNPROFILESTORE_SERVICE_NAME, e
+ );
+ },
+ );
+
info!("Successfully registered Keystore 2.0 service.");
info!("Joining thread pool now.");
diff --git a/keystore2/src/km_compat/Android.bp b/keystore2/src/km_compat/Android.bp
index 2180935..541788e 100644
--- a/keystore2/src/km_compat/Android.bp
+++ b/keystore2/src/km_compat/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
rust_library {
name: "libkeystore2_km_compat",
crate_name: "keystore2_km_compat",
@@ -58,6 +67,7 @@
"libcrypto",
"libhidlbase",
"libkeymaster4_1support",
+ "libkeymint",
"libkeymint_support",
"libkeystore2_crypto",
"libutils",
@@ -110,6 +120,7 @@
"libkeymint_support",
"libkeystore2_crypto",
"libkm_compat",
+ "libkm_compat_service",
"libutils",
],
}
diff --git a/keystore2/src/km_compat/certificate_test.cpp b/keystore2/src/km_compat/certificate_test.cpp
index d6bece7..06cb0cb 100644
--- a/keystore2/src/km_compat/certificate_test.cpp
+++ b/keystore2/src/km_compat/certificate_test.cpp
@@ -23,6 +23,7 @@
#include <aidl/android/hardware/security/keymint/BlockMode.h>
#include <aidl/android/hardware/security/keymint/Digest.h>
#include <aidl/android/hardware/security/keymint/PaddingMode.h>
+#include <android/binder_manager.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
@@ -39,18 +40,31 @@
using ::aidl::android::hardware::security::keymint::PaddingMode;
using ::aidl::android::hardware::security::keymint::SecurityLevel;
using ::aidl::android::hardware::security::keymint::Tag;
+using ::aidl::android::security::compat::IKeystoreCompatService;
namespace KMV1 = ::aidl::android::hardware::security::keymint;
-static std::variant<std::vector<Certificate>, ScopedAStatus>
-getCertificate(const std::vector<KeyParameter>& keyParams) {
- static std::shared_ptr<KeyMintDevice> device =
- KeyMintDevice::createKeyMintDevice(SecurityLevel::TRUSTED_ENVIRONMENT);
+extern "C" int32_t addKeyMintDeviceService();
+
+static std::variant<std::shared_ptr<IKeyMintDevice>, ScopedAStatus> getDevice() {
+ addKeyMintDeviceService();
+ std::shared_ptr<IKeyMintDevice> device;
+ auto service = IKeystoreCompatService::fromBinder(
+ ndk::SpAIBinder(AServiceManager_getService("android.security.compat")));
+ if (!service) {
+ return ScopedAStatus::fromStatus(STATUS_NAME_NOT_FOUND);
+ }
+ service->getKeyMintDevice(SecurityLevel::TRUSTED_ENVIRONMENT, &device);
if (!device) {
return ScopedAStatus::fromStatus(STATUS_NAME_NOT_FOUND);
}
+ return device;
+}
+
+static std::variant<std::vector<Certificate>, ScopedAStatus>
+getCertificate(std::shared_ptr<IKeyMintDevice> device, const std::vector<KeyParameter>& keyParams) {
KeyCreationResult creationResult;
- auto status = device->generateKey(keyParams, &creationResult);
+ auto status = device->generateKey(keyParams, std::nullopt /* attest_key */, &creationResult);
if (!status.isOk()) {
return status;
}
@@ -76,6 +90,8 @@
KMV1::makeKeyParameter(KMV1::TAG_ALGORITHM, Algorithm::RSA),
KMV1::makeKeyParameter(KMV1::TAG_KEY_SIZE, 2048),
KMV1::makeKeyParameter(KMV1::TAG_RSA_PUBLIC_EXPONENT, 65537),
+ KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_BEFORE, 0),
+ KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_AFTER, 253402300799000),
});
keyParams.insert(keyParams.end(), extraParams.begin(), extraParams.end());
return keyParams;
@@ -87,10 +103,12 @@
KMV1::makeKeyParameter(KMV1::TAG_PADDING, PaddingMode::RSA_PSS),
KMV1::makeKeyParameter(KMV1::TAG_NO_AUTH_REQUIRED),
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::SIGN),
- KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::ENCRYPT),
});
- auto result = getCertificate(keyParams);
- ensureCertChainSize(result, 1);
+ auto device = getDevice();
+ if (std::holds_alternative<std::shared_ptr<IKeyMintDevice>>(device)) {
+ auto result = getCertificate(std::get<std::shared_ptr<IKeyMintDevice>>(device), keyParams);
+ ensureCertChainSize(result, 1);
+ }
}
TEST(CertificateTest, TestAES) {
@@ -101,8 +119,11 @@
KMV1::makeKeyParameter(KMV1::TAG_PADDING, PaddingMode::NONE),
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::ENCRYPT),
};
- auto result = getCertificate(keyParams);
- ensureCertChainSize(result, 0);
+ auto device = getDevice();
+ if (std::holds_alternative<std::shared_ptr<IKeyMintDevice>>(device)) {
+ auto result = getCertificate(std::get<std::shared_ptr<IKeyMintDevice>>(device), keyParams);
+ ensureCertChainSize(result, 0);
+ }
}
TEST(CertificateTest, TestAttestation) {
@@ -111,9 +132,12 @@
KMV1::makeKeyParameter(KMV1::TAG_ATTESTATION_CHALLENGE, 42),
KMV1::makeKeyParameter(KMV1::TAG_ATTESTATION_APPLICATION_ID, 42),
});
- auto result = getCertificate(keyParams);
- ensureCertChainSize(result, 3);
- verify(std::get<std::vector<Certificate>>(result).back());
+ auto device = getDevice();
+ if (std::holds_alternative<std::shared_ptr<IKeyMintDevice>>(device)) {
+ auto result = getCertificate(std::get<std::shared_ptr<IKeyMintDevice>>(device), keyParams);
+ ensureCertChainSize(result, 3);
+ verify(std::get<std::vector<Certificate>>(result).back());
+ }
}
TEST(CertificateTest, TestRSAKeygenNoEncryptNoAuthRequired) {
@@ -123,9 +147,12 @@
KMV1::makeKeyParameter(KMV1::TAG_NO_AUTH_REQUIRED, true),
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::SIGN),
});
- auto result = getCertificate(keyParams);
- ensureCertChainSize(result, 1);
- verify(std::get<std::vector<Certificate>>(result)[0]);
+ auto device = getDevice();
+ if (std::holds_alternative<std::shared_ptr<IKeyMintDevice>>(device)) {
+ auto result = getCertificate(std::get<std::shared_ptr<IKeyMintDevice>>(device), keyParams);
+ ensureCertChainSize(result, 1);
+ verify(std::get<std::vector<Certificate>>(result)[0]);
+ }
}
TEST(CertificateTest, TestRSAKeygenNoEncryptAuthRequired) {
@@ -134,6 +161,9 @@
KMV1::makeKeyParameter(KMV1::TAG_PADDING, PaddingMode::RSA_PSS),
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::SIGN),
});
- auto result = getCertificate(keyParams);
- ensureCertChainSize(result, 1);
+ auto device = getDevice();
+ if (std::holds_alternative<std::shared_ptr<IKeyMintDevice>>(device)) {
+ auto result = getCertificate(std::get<std::shared_ptr<IKeyMintDevice>>(device), keyParams);
+ ensureCertChainSize(result, 1);
+ }
}
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 429a038..3439d2f 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -17,9 +17,11 @@
#include "km_compat.h"
#include "km_compat_type_conversion.h"
+#include <AndroidKeyMintDevice.h>
#include <aidl/android/hardware/security/keymint/Algorithm.h>
#include <aidl/android/hardware/security/keymint/Digest.h>
#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/KeyParameterValue.h>
#include <aidl/android/hardware/security/keymint/PaddingMode.h>
#include <aidl/android/system/keystore2/ResponseCode.h>
#include <android-base/logging.h>
@@ -35,7 +37,9 @@
#include "certificate_utils.h"
using ::aidl::android::hardware::security::keymint::Algorithm;
+using ::aidl::android::hardware::security::keymint::CreateKeyMintDevice;
using ::aidl::android::hardware::security::keymint::Digest;
+using ::aidl::android::hardware::security::keymint::KeyParameterValue;
using ::aidl::android::hardware::security::keymint::PaddingMode;
using ::aidl::android::hardware::security::keymint::Tag;
using ::aidl::android::system::keystore2::ResponseCode;
@@ -134,6 +138,77 @@
}
}
+// Size of prefix for blobs, see keyBlobPrefix().
+//
+const size_t kKeyBlobPrefixSize = 8;
+
+// Magic used in blob prefix, see keyBlobPrefix().
+//
+const uint8_t kKeyBlobMagic[7] = {'p', 'K', 'M', 'b', 'l', 'o', 'b'};
+
+// Prefixes a keyblob returned by e.g. generateKey() with information on whether it
+// originated from the real underlying KeyMaster HAL or from soft-KeyMint.
+//
+// When dealing with a keyblob, use prefixedKeyBlobRemovePrefix() to remove the
+// prefix and prefixedKeyBlobIsSoftKeyMint() to determine its origin.
+//
+// Note how the prefix itself has a magic marker ("pKMblob") which can be used
+// to identify if a blob has a prefix at all (it's assumed that any valid blob
+// from KeyMint or KeyMaster HALs never starts with the magic). This is needed
+// because blobs persisted to disk prior to using this code will not have the
+// prefix and in that case we want prefixedKeyBlobRemovePrefix() to still work.
+//
+std::vector<uint8_t> keyBlobPrefix(const std::vector<uint8_t>& blob, bool isSoftKeyMint) {
+ std::vector<uint8_t> result;
+ result.reserve(blob.size() + kKeyBlobPrefixSize);
+ result.insert(result.begin(), kKeyBlobMagic, kKeyBlobMagic + sizeof kKeyBlobMagic);
+ result.push_back(isSoftKeyMint ? 1 : 0);
+ std::copy(blob.begin(), blob.end(), std::back_inserter(result));
+ return result;
+}
+
+// Helper for prefixedKeyBlobRemovePrefix() and prefixedKeyBlobIsSoftKeyMint().
+//
+// First bool is whether there's a valid prefix. If there is, the second bool is
+// the |isSoftKeyMint| value of the prefix
+//
+std::pair<bool, bool> prefixedKeyBlobParsePrefix(const std::vector<uint8_t>& prefixedBlob) {
+ // Having a unprefixed blob is not that uncommon, for example all devices
+ // upgrading to keystore2 (so e.g. upgrading to Android 12) will have
+ // unprefixed blobs. So don't spew warnings/errors in this case...
+ if (prefixedBlob.size() < kKeyBlobPrefixSize) {
+ return std::make_pair(false, false);
+ }
+ if (std::memcmp(prefixedBlob.data(), kKeyBlobMagic, sizeof kKeyBlobMagic) != 0) {
+ return std::make_pair(false, false);
+ }
+ if (prefixedBlob[kKeyBlobPrefixSize - 1] != 0 && prefixedBlob[kKeyBlobPrefixSize - 1] != 1) {
+ return std::make_pair(false, false);
+ }
+ bool isSoftKeyMint = (prefixedBlob[kKeyBlobPrefixSize - 1] == 1);
+ return std::make_pair(true, isSoftKeyMint);
+}
+
+// Returns the prefix from a blob. If there's no prefix, returns the passed-in blob.
+//
+std::vector<uint8_t> prefixedKeyBlobRemovePrefix(const std::vector<uint8_t>& prefixedBlob) {
+ auto parsed = prefixedKeyBlobParsePrefix(prefixedBlob);
+ if (!parsed.first) {
+ // Not actually prefixed, blob was probably persisted to disk prior to the
+ // prefixing code being introduced.
+ return prefixedBlob;
+ }
+ return std::vector<uint8_t>(prefixedBlob.begin() + kKeyBlobPrefixSize, prefixedBlob.end());
+}
+
+// Returns true if the blob's origin is soft-KeyMint, false otherwise or if there
+// is no prefix on the passed-in blob.
+//
+bool prefixedKeyBlobIsSoftKeyMint(const std::vector<uint8_t>& prefixedBlob) {
+ auto parsed = prefixedKeyBlobParsePrefix(prefixedBlob);
+ return parsed.second;
+}
+
/*
* Returns true if the parameter is not understood by KM 4.1 and older but can be enforced by
* Keystore. These parameters need to be included in the returned KeyCharacteristics, but will not
@@ -226,7 +301,9 @@
if (securityLevel == KeyMintSecurityLevel::SOFTWARE) {
// If the security level of the backend is `software` we expect the hardware enforced list
// to be empty. Log a warning otherwise.
- CHECK(legacyKc.hardwareEnforced.size() == 0);
+ if (legacyKc.hardwareEnforced.size() != 0) {
+ LOG(WARNING) << "Unexpected hardware enforced parameters.";
+ }
return {keystoreEnforced};
}
@@ -239,26 +316,31 @@
return static_cast<V4_0_KeyFormat>(kf);
}
-static V4_0_HardwareAuthToken convertAuthTokenToLegacy(const HardwareAuthToken& at) {
+static V4_0_HardwareAuthToken convertAuthTokenToLegacy(const std::optional<HardwareAuthToken>& at) {
+ if (!at) return {};
+
V4_0_HardwareAuthToken legacyAt;
- legacyAt.challenge = at.challenge;
- legacyAt.userId = at.userId;
- legacyAt.authenticatorId = at.authenticatorId;
+ legacyAt.challenge = at->challenge;
+ legacyAt.userId = at->userId;
+ legacyAt.authenticatorId = at->authenticatorId;
legacyAt.authenticatorType =
static_cast<::android::hardware::keymaster::V4_0::HardwareAuthenticatorType>(
- at.authenticatorType);
- legacyAt.timestamp = at.timestamp.milliSeconds;
- legacyAt.mac = at.mac;
+ at->authenticatorType);
+ legacyAt.timestamp = at->timestamp.milliSeconds;
+ legacyAt.mac = at->mac;
return legacyAt;
}
-static V4_0_VerificationToken convertTimestampTokenToLegacy(const TimeStampToken& tst) {
+static V4_0_VerificationToken
+convertTimestampTokenToLegacy(const std::optional<TimeStampToken>& tst) {
+ if (!tst) return {};
+
V4_0_VerificationToken legacyVt;
- legacyVt.challenge = tst.challenge;
- legacyVt.timestamp = tst.timestamp.milliSeconds;
+ legacyVt.challenge = tst->challenge;
+ legacyVt.timestamp = tst->timestamp.milliSeconds;
// Legacy verification tokens were always minted by TEE.
legacyVt.securityLevel = V4_0::SecurityLevel::TRUSTED_ENVIRONMENT;
- legacyVt.mac = tst.mac;
+ legacyVt.mac = tst->mac;
return legacyVt;
}
@@ -334,14 +416,38 @@
}
ScopedAStatus KeyMintDevice::generateKey(const std::vector<KeyParameter>& inKeyParams,
+ const std::optional<AttestationKey>& in_attestationKey,
KeyCreationResult* out_creationResult) {
+
+ // Since KeyMaster doesn't support ECDH, route all key creation requests to
+ // soft-KeyMint if and only an ECDH key is requested.
+ //
+ // For this to work we'll need to also route begin() and deleteKey() calls to
+ // soft-KM. In order to do that, we'll prefix all keyblobs with whether it was
+ // created by the real underlying KeyMaster HAL or whether it was created by
+ // soft-KeyMint.
+ //
+ // See keyBlobPrefix() for more discussion.
+ //
+ for (const auto& keyParam : inKeyParams) {
+ if (keyParam.tag == Tag::PURPOSE &&
+ keyParam.value.get<KeyParameterValue::Tag::keyPurpose>() == KeyPurpose::AGREE_KEY) {
+ auto ret =
+ softKeyMintDevice_->generateKey(inKeyParams, in_attestationKey, out_creationResult);
+ if (ret.isOk()) {
+ out_creationResult->keyBlob = keyBlobPrefix(out_creationResult->keyBlob, true);
+ }
+ return ret;
+ }
+ }
+
auto legacyKeyGenParams = convertKeyParametersToLegacy(extractGenerationParams(inKeyParams));
KMV1::ErrorCode errorCode;
auto result = mDevice->generateKey(
legacyKeyGenParams, [&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
const V4_0_KeyCharacteristics& keyCharacteristics) {
errorCode = convert(error);
- out_creationResult->keyBlob = keyBlob;
+ out_creationResult->keyBlob = keyBlobPrefix(keyBlob, false);
out_creationResult->keyCharacteristics =
processLegacyCharacteristics(securityLevel_, inKeyParams, keyCharacteristics);
});
@@ -368,6 +474,7 @@
ScopedAStatus KeyMintDevice::importKey(const std::vector<KeyParameter>& inKeyParams,
KeyFormat in_inKeyFormat,
const std::vector<uint8_t>& in_inKeyData,
+ const std::optional<AttestationKey>& /* in_attestationKey */,
KeyCreationResult* out_creationResult) {
auto legacyKeyGENParams = convertKeyParametersToLegacy(extractGenerationParams(inKeyParams));
auto legacyKeyFormat = convertKeyFormatToLegacy(in_inKeyFormat);
@@ -376,7 +483,8 @@
[&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
const V4_0_KeyCharacteristics& keyCharacteristics) {
errorCode = convert(error);
- out_creationResult->keyBlob = keyBlob;
+ out_creationResult->keyBlob =
+ keyBlobPrefix(keyBlob, false);
out_creationResult->keyCharacteristics =
processLegacyCharacteristics(
securityLevel_, inKeyParams, keyCharacteristics);
@@ -401,20 +509,30 @@
return convertErrorCode(errorCode);
}
-ScopedAStatus KeyMintDevice::importWrappedKey(
- const std::vector<uint8_t>& in_inWrappedKeyData,
- const std::vector<uint8_t>& in_inWrappingKeyBlob, const std::vector<uint8_t>& in_inMaskingKey,
- const std::vector<KeyParameter>& in_inUnwrappingParams, int64_t in_inPasswordSid,
- int64_t in_inBiometricSid, KeyCreationResult* out_creationResult) {
+ScopedAStatus
+KeyMintDevice::importWrappedKey(const std::vector<uint8_t>& in_inWrappedKeyData,
+ const std::vector<uint8_t>& in_inPrefixedWrappingKeyBlob,
+ const std::vector<uint8_t>& in_inMaskingKey,
+ const std::vector<KeyParameter>& in_inUnwrappingParams,
+ int64_t in_inPasswordSid, int64_t in_inBiometricSid,
+ KeyCreationResult* out_creationResult) {
+ const std::vector<uint8_t>& wrappingKeyBlob =
+ prefixedKeyBlobRemovePrefix(in_inPrefixedWrappingKeyBlob);
+ if (prefixedKeyBlobIsSoftKeyMint(in_inPrefixedWrappingKeyBlob)) {
+ return softKeyMintDevice_->importWrappedKey(
+ in_inWrappedKeyData, wrappingKeyBlob, in_inMaskingKey, in_inUnwrappingParams,
+ in_inPasswordSid, in_inBiometricSid, out_creationResult);
+ }
+
auto legacyUnwrappingParams = convertKeyParametersToLegacy(in_inUnwrappingParams);
KMV1::ErrorCode errorCode;
auto result = mDevice->importWrappedKey(
- in_inWrappedKeyData, in_inWrappingKeyBlob, in_inMaskingKey, legacyUnwrappingParams,
+ in_inWrappedKeyData, wrappingKeyBlob, in_inMaskingKey, legacyUnwrappingParams,
in_inPasswordSid, in_inBiometricSid,
[&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
const V4_0_KeyCharacteristics& keyCharacteristics) {
errorCode = convert(error);
- out_creationResult->keyBlob = keyBlob;
+ out_creationResult->keyBlob = keyBlobPrefix(keyBlob, false);
out_creationResult->keyCharacteristics =
processLegacyCharacteristics(securityLevel_, {}, keyCharacteristics);
});
@@ -443,8 +561,13 @@
return convertErrorCode(errorCode);
}
-ScopedAStatus KeyMintDevice::deleteKey(const std::vector<uint8_t>& in_inKeyBlob) {
- auto result = mDevice->deleteKey(in_inKeyBlob);
+ScopedAStatus KeyMintDevice::deleteKey(const std::vector<uint8_t>& prefixedKeyBlob) {
+ const std::vector<uint8_t>& keyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob);
+ if (prefixedKeyBlobIsSoftKeyMint(prefixedKeyBlob)) {
+ return softKeyMintDevice_->deleteKey(keyBlob);
+ }
+
+ auto result = mDevice->deleteKey(keyBlob);
if (!result.isOk()) {
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR);
@@ -468,13 +591,20 @@
}
ScopedAStatus KeyMintDevice::begin(KeyPurpose in_inPurpose,
- const std::vector<uint8_t>& in_inKeyBlob,
+ const std::vector<uint8_t>& prefixedKeyBlob,
const std::vector<KeyParameter>& in_inParams,
const HardwareAuthToken& in_inAuthToken,
BeginResult* _aidl_return) {
if (!mOperationSlots.claimSlot()) {
return convertErrorCode(V4_0_ErrorCode::TOO_MANY_OPERATIONS);
}
+
+ const std::vector<uint8_t>& in_inKeyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob);
+ if (prefixedKeyBlobIsSoftKeyMint(prefixedKeyBlob)) {
+ return softKeyMintDevice_->begin(in_inPurpose, in_inKeyBlob, in_inParams, in_inAuthToken,
+ _aidl_return);
+ }
+
auto legacyPurpose =
static_cast<::android::hardware::keymaster::V4_0::KeyPurpose>(in_inPurpose);
auto legacyParams = convertKeyParametersToLegacy(in_inParams);
@@ -500,81 +630,113 @@
return convertErrorCode(errorCode);
}
-ScopedAStatus KeyMintOperation::update(const std::optional<KeyParameterArray>& in_inParams,
- const std::optional<std::vector<uint8_t>>& in_input,
- const std::optional<HardwareAuthToken>& in_inAuthToken,
- const std::optional<TimeStampToken>& in_inTimeStampToken,
- std::optional<KeyParameterArray>* out_outParams,
- std::optional<ByteArray>* out_output,
- int32_t* _aidl_return) {
- std::vector<V4_0_KeyParameter> legacyParams;
- if (in_inParams.has_value()) {
- legacyParams = convertKeyParametersToLegacy(in_inParams.value().params);
+ScopedAStatus KeyMintDevice::deviceLocked(bool passwordOnly,
+ const std::optional<TimeStampToken>& timestampToken) {
+ V4_0_VerificationToken token;
+ if (timestampToken.has_value()) {
+ token = convertTimestampTokenToLegacy(timestampToken.value());
}
- auto input = in_input.value_or(std::vector<uint8_t>());
- V4_0_HardwareAuthToken authToken;
- if (in_inAuthToken.has_value()) {
- authToken = convertAuthTokenToLegacy(in_inAuthToken.value());
+ auto ret = mDevice->deviceLocked(passwordOnly, token);
+ if (!ret.isOk()) {
+ return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR);
+ } else {
+ return convertErrorCode(KMV1::ErrorCode::OK);
}
- V4_0_VerificationToken verificationToken;
- if (in_inTimeStampToken.has_value()) {
- verificationToken = convertTimestampTokenToLegacy(in_inTimeStampToken.value());
+}
+
+ScopedAStatus KeyMintDevice::earlyBootEnded() {
+ auto ret = mDevice->earlyBootEnded();
+ if (!ret.isOk()) {
+ return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR);
+ } else {
+ return convertErrorCode(KMV1::ErrorCode::OK);
}
+}
+
+ScopedAStatus KeyMintDevice::performOperation(const std::vector<uint8_t>& /* request */,
+ std::vector<uint8_t>* /* response */) {
+ return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED);
+}
+
+ScopedAStatus KeyMintOperation::updateAad(const std::vector<uint8_t>& input,
+ const std::optional<HardwareAuthToken>& optAuthToken,
+ const std::optional<TimeStampToken>& optTimeStampToken) {
+ V4_0_HardwareAuthToken authToken = convertAuthTokenToLegacy(optAuthToken);
+ V4_0_VerificationToken verificationToken = convertTimestampTokenToLegacy(optTimeStampToken);
KMV1::ErrorCode errorCode;
auto result = mDevice->update(
- mOperationHandle, legacyParams, input, authToken, verificationToken,
- [&](V4_0_ErrorCode error, uint32_t inputConsumed,
- const hidl_vec<V4_0_KeyParameter>& outParams, const hidl_vec<uint8_t>& output) {
- errorCode = convert(error);
- out_outParams->emplace();
- out_outParams->value().params = convertKeyParametersFromLegacy(outParams);
- out_output->emplace();
- out_output->value().data = output;
- *_aidl_return = inputConsumed;
- });
+ mOperationHandle, {V4_0::makeKeyParameter(V4_0::TAG_ASSOCIATED_DATA, input)}, {}, authToken,
+ verificationToken,
+ [&](V4_0_ErrorCode error, auto, auto, auto) { errorCode = convert(error); });
if (!result.isOk()) {
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
errorCode = KMV1::ErrorCode::UNKNOWN_ERROR;
}
- if (errorCode != KMV1::ErrorCode::OK) {
- mOperationSlot.freeSlot();
- }
+ if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot();
+
return convertErrorCode(errorCode);
}
-ScopedAStatus KeyMintOperation::finish(const std::optional<KeyParameterArray>& in_inParams,
- const std::optional<std::vector<uint8_t>>& in_input,
- const std::optional<std::vector<uint8_t>>& in_inSignature,
- const std::optional<HardwareAuthToken>& in_authToken,
- const std::optional<TimeStampToken>& in_inTimeStampToken,
- std::optional<KeyParameterArray>* out_outParams,
- std::vector<uint8_t>* _aidl_return) {
- KMV1::ErrorCode errorCode;
- std::vector<V4_0_KeyParameter> legacyParams;
- if (in_inParams.has_value()) {
- legacyParams = convertKeyParametersToLegacy(in_inParams.value().params);
+ScopedAStatus KeyMintOperation::update(const std::vector<uint8_t>& input,
+ const std::optional<HardwareAuthToken>& optAuthToken,
+ const std::optional<TimeStampToken>& optTimeStampToken,
+ std::vector<uint8_t>* out_output) {
+ V4_0_HardwareAuthToken authToken = convertAuthTokenToLegacy(optAuthToken);
+ V4_0_VerificationToken verificationToken = convertTimestampTokenToLegacy(optTimeStampToken);
+
+ size_t inputPos = 0;
+ *out_output = {};
+ KMV1::ErrorCode errorCode = KMV1::ErrorCode::OK;
+
+ while (inputPos < input.size() && errorCode == KMV1::ErrorCode::OK) {
+ auto result =
+ mDevice->update(mOperationHandle, {} /* inParams */,
+ {input.begin() + inputPos, input.end()}, authToken, verificationToken,
+ [&](V4_0_ErrorCode error, uint32_t inputConsumed, auto /* outParams */,
+ const hidl_vec<uint8_t>& output) {
+ errorCode = convert(error);
+ out_output->insert(out_output->end(), output.begin(), output.end());
+ inputPos += inputConsumed;
+ });
+
+ if (!result.isOk()) {
+ LOG(ERROR) << __func__ << " transaction failed. " << result.description();
+ errorCode = KMV1::ErrorCode::UNKNOWN_ERROR;
+ }
}
+
+ if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot();
+
+ return convertErrorCode(errorCode);
+}
+
+ScopedAStatus
+KeyMintOperation::finish(const std::optional<std::vector<uint8_t>>& in_input,
+ const std::optional<std::vector<uint8_t>>& in_signature,
+ const std::optional<HardwareAuthToken>& in_authToken,
+ const std::optional<TimeStampToken>& in_timeStampToken,
+ const std::optional<std::vector<uint8_t>>& in_confirmationToken,
+ std::vector<uint8_t>* out_output) {
auto input = in_input.value_or(std::vector<uint8_t>());
- auto signature = in_inSignature.value_or(std::vector<uint8_t>());
- V4_0_HardwareAuthToken authToken;
- if (in_authToken.has_value()) {
- authToken = convertAuthTokenToLegacy(in_authToken.value());
+ auto signature = in_signature.value_or(std::vector<uint8_t>());
+ V4_0_HardwareAuthToken authToken = convertAuthTokenToLegacy(in_authToken);
+ V4_0_VerificationToken verificationToken = convertTimestampTokenToLegacy(in_timeStampToken);
+
+ std::vector<V4_0_KeyParameter> inParams;
+ if (in_confirmationToken) {
+ inParams.push_back(makeKeyParameter(V4_0::TAG_CONFIRMATION_TOKEN, *in_confirmationToken));
}
- V4_0_VerificationToken verificationToken;
- if (in_inTimeStampToken.has_value()) {
- verificationToken = convertTimestampTokenToLegacy(in_inTimeStampToken.value());
- }
+
+ KMV1::ErrorCode errorCode;
auto result = mDevice->finish(
- mOperationHandle, legacyParams, input, signature, authToken, verificationToken,
- [&](V4_0_ErrorCode error, const hidl_vec<V4_0_KeyParameter>& outParams,
- const hidl_vec<uint8_t>& output) {
+ mOperationHandle, {} /* inParams */, input, signature, authToken, verificationToken,
+ [&](V4_0_ErrorCode error, auto /* outParams */, const hidl_vec<uint8_t>& output) {
errorCode = convert(error);
- out_outParams->emplace();
- out_outParams->value().params = convertKeyParametersFromLegacy(outParams);
- *_aidl_return = output;
+ *out_output = output;
});
+
mOperationSlot.freeSlot();
if (!result.isOk()) {
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
@@ -811,7 +973,8 @@
std::optional<KMV1::ErrorCode>
KeyMintDevice::signCertificate(const std::vector<KeyParameter>& keyParams,
- const std::vector<uint8_t>& keyBlob, X509* cert) {
+ const std::vector<uint8_t>& prefixedKeyBlob, X509* cert) {
+
auto algorithm = getParam(keyParams, KMV1::TAG_ALGORITHM);
auto algoOrError = getKeystoreAlgorithm(*algorithm);
if (std::holds_alternative<KMV1::ErrorCode>(algoOrError)) {
@@ -846,23 +1009,20 @@
kps.push_back(KMV1::makeKeyParameter(KMV1::TAG_PADDING, origPadding));
}
BeginResult beginResult;
- auto error = begin(KeyPurpose::SIGN, keyBlob, kps, HardwareAuthToken(), &beginResult);
+ auto error =
+ begin(KeyPurpose::SIGN, prefixedKeyBlob, kps, HardwareAuthToken(), &beginResult);
if (!error.isOk()) {
errorCode = toErrorCode(error);
return std::vector<uint8_t>();
}
- std::optional<KeyParameterArray> outParams;
- std::optional<ByteArray> outByte;
- int32_t status;
- error = beginResult.operation->update(std::nullopt, dataVec, std::nullopt, std::nullopt,
- &outParams, &outByte, &status);
- if (!error.isOk()) {
- errorCode = toErrorCode(error);
- return std::vector<uint8_t>();
- }
+
std::vector<uint8_t> result;
- error = beginResult.operation->finish(std::nullopt, std::nullopt, std::nullopt,
- std::nullopt, std::nullopt, &outParams, &result);
+ error = beginResult.operation->finish(dataVec, //
+ {} /* signature */, //
+ {} /* authToken */, //
+ {} /* timestampToken */, //
+ {} /* confirmationToken */, //
+ &result);
if (!error.isOk()) {
errorCode = toErrorCode(error);
return std::vector<uint8_t>();
@@ -883,7 +1043,9 @@
std::variant<std::vector<Certificate>, KMV1::ErrorCode>
KeyMintDevice::getCertificate(const std::vector<KeyParameter>& keyParams,
- const std::vector<uint8_t>& keyBlob) {
+ const std::vector<uint8_t>& prefixedKeyBlob) {
+ const std::vector<uint8_t>& keyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob);
+
// There are no certificates for symmetric keys.
auto algorithm = getParam(keyParams, KMV1::TAG_ALGORITHM);
if (!algorithm) {
@@ -981,14 +1143,14 @@
// Copied from system/security/keystore/include/keystore/keymaster_types.h.
// Changing this namespace alias will change the keymaster version.
-namespace keymaster = ::android::hardware::keymaster::V4_1;
+namespace keymasterNs = ::android::hardware::keymaster::V4_1;
-using keymaster::SecurityLevel;
+using keymasterNs::SecurityLevel;
// Copied from system/security/keystore/KeyStore.h.
using ::android::sp;
-using keymaster::support::Keymaster;
+using keymasterNs::support::Keymaster;
template <typename T, size_t count> class Devices : public std::array<T, count> {
public:
@@ -1013,8 +1175,8 @@
// Copied from system/security/keystore/keystore_main.cpp.
using ::android::hardware::hidl_string;
-using keymaster::support::Keymaster3;
-using keymaster::support::Keymaster4;
+using keymasterNs::support::Keymaster3;
+using keymasterNs::support::Keymaster4;
template <typename Wrapper>
KeymasterDevices enumerateKeymasterDevices(IServiceManager* serviceManager) {
@@ -1090,12 +1252,14 @@
// Constructors and helpers.
KeyMintDevice::KeyMintDevice(sp<Keymaster> device, KeyMintSecurityLevel securityLevel)
- : mDevice(device) {
+ : mDevice(device), securityLevel_(securityLevel) {
if (securityLevel == KeyMintSecurityLevel::STRONGBOX) {
- mOperationSlots.setNumFreeSlots(3);
+ setNumFreeSlots(3);
} else {
- mOperationSlots.setNumFreeSlots(15);
+ setNumFreeSlots(15);
}
+
+ softKeyMintDevice_.reset(CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE));
}
sp<Keymaster> getDevice(KeyMintSecurityLevel securityLevel) {
diff --git a/keystore2/src/km_compat/km_compat.h b/keystore2/src/km_compat/km_compat.h
index 5637b58..b48a226 100644
--- a/keystore2/src/km_compat/km_compat.h
+++ b/keystore2/src/km_compat/km_compat.h
@@ -28,8 +28,8 @@
#include "certificate_utils.h"
+using ::aidl::android::hardware::security::keymint::AttestationKey;
using ::aidl::android::hardware::security::keymint::BeginResult;
-using ::aidl::android::hardware::security::keymint::ByteArray;
using ::aidl::android::hardware::security::keymint::Certificate;
using ::aidl::android::hardware::security::keymint::HardwareAuthToken;
using ::aidl::android::hardware::security::keymint::KeyCharacteristics;
@@ -37,7 +37,6 @@
using ::aidl::android::hardware::security::keymint::KeyFormat;
using ::aidl::android::hardware::security::keymint::KeyMintHardwareInfo;
using ::aidl::android::hardware::security::keymint::KeyParameter;
-using ::aidl::android::hardware::security::keymint::KeyParameterArray;
using ::aidl::android::hardware::security::keymint::KeyPurpose;
using KeyMintSecurityLevel = ::aidl::android::hardware::security::keymint::SecurityLevel;
using V4_0_ErrorCode = ::android::hardware::keymaster::V4_0::ErrorCode;
@@ -90,9 +89,11 @@
ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* _aidl_return) override;
ScopedAStatus addRngEntropy(const std::vector<uint8_t>& in_data) override;
ScopedAStatus generateKey(const std::vector<KeyParameter>& in_keyParams,
+ const std::optional<AttestationKey>& in_attestationKey,
KeyCreationResult* out_creationResult) override;
ScopedAStatus importKey(const std::vector<KeyParameter>& in_inKeyParams,
KeyFormat in_inKeyFormat, const std::vector<uint8_t>& in_inKeyData,
+ const std::optional<AttestationKey>& in_attestationKey,
KeyCreationResult* out_creationResult) override;
ScopedAStatus importWrappedKey(const std::vector<uint8_t>& in_inWrappedKeyData,
const std::vector<uint8_t>& in_inWrappingKeyBlob,
@@ -110,10 +111,15 @@
const std::vector<KeyParameter>& in_inParams,
const HardwareAuthToken& in_inAuthToken,
BeginResult* _aidl_return) override;
+ ScopedAStatus deviceLocked(bool passwordOnly,
+ const std::optional<TimeStampToken>& timestampToken) override;
+ ScopedAStatus earlyBootEnded() override;
+
+ ScopedAStatus performOperation(const std::vector<uint8_t>& request,
+ std::vector<uint8_t>* response) override;
// These are public to allow testing code to use them directly.
// This class should not be used publicly anyway.
-
std::variant<std::vector<Certificate>, KMV1_ErrorCode>
getCertificate(const std::vector<KeyParameter>& keyParams, const std::vector<uint8_t>& keyBlob);
@@ -123,6 +129,9 @@
std::optional<KMV1_ErrorCode> signCertificate(const std::vector<KeyParameter>& keyParams,
const std::vector<uint8_t>& keyBlob, X509* cert);
KeyMintSecurityLevel securityLevel_;
+
+ // Software-based KeyMint device used to implement ECDH.
+ std::shared_ptr<IKeyMintDevice> softKeyMintDevice_;
};
class KeyMintOperation : public aidl::android::hardware::security::keymint::BnKeyMintOperation {
@@ -137,19 +146,22 @@
: mDevice(device), mOperationHandle(operationHandle), mOperationSlot(slots, isActive) {}
~KeyMintOperation();
- ScopedAStatus update(const std::optional<KeyParameterArray>& in_inParams,
- const std::optional<std::vector<uint8_t>>& in_input,
- const std::optional<HardwareAuthToken>& in_inAuthToken,
- const std::optional<TimeStampToken>& in_inTimestampToken,
- std::optional<KeyParameterArray>* out_outParams,
- std::optional<ByteArray>* out_output, int32_t* _aidl_return);
- ScopedAStatus finish(const std::optional<KeyParameterArray>& in_inParams,
- const std::optional<std::vector<uint8_t>>& in_input,
- const std::optional<std::vector<uint8_t>>& in_inSignature,
- const std::optional<HardwareAuthToken>& in_authToken,
- const std::optional<TimeStampToken>& in_inTimestampToken,
- std::optional<KeyParameterArray>* out_outParams,
- std::vector<uint8_t>* _aidl_return);
+ ScopedAStatus updateAad(const std::vector<uint8_t>& input,
+ const std::optional<HardwareAuthToken>& authToken,
+ const std::optional<TimeStampToken>& timestampToken) override;
+
+ ScopedAStatus update(const std::vector<uint8_t>& input,
+ const std::optional<HardwareAuthToken>& authToken,
+ const std::optional<TimeStampToken>& timestampToken,
+ std::vector<uint8_t>* output) override;
+
+ ScopedAStatus finish(const std::optional<std::vector<uint8_t>>& input,
+ const std::optional<std::vector<uint8_t>>& signature,
+ const std::optional<HardwareAuthToken>& authToken,
+ const std::optional<TimeStampToken>& timeStampToken,
+ const std::optional<std::vector<uint8_t>>& confirmationToken,
+ std::vector<uint8_t>* output) override;
+
ScopedAStatus abort();
};
diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h
index b36b78a..c2b4669 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -740,6 +740,9 @@
case KMV1::Tag::CERTIFICATE_NOT_AFTER:
// These tags do not exist in KM < KeyMint 1.0.
break;
+ case KMV1::Tag::MAX_BOOT_LEVEL:
+ // Does not exist in API level 30 or below.
+ break;
}
return V4_0::KeyParameter{.tag = V4_0::Tag::INVALID};
}
diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs
index eca0a85..6e27b5c 100644
--- a/keystore2/src/km_compat/lib.rs
+++ b/keystore2/src/km_compat/lib.rs
@@ -31,8 +31,8 @@
Algorithm::Algorithm, BeginResult::BeginResult, BlockMode::BlockMode, Digest::Digest,
ErrorCode::ErrorCode, HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::IKeyMintDevice,
KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat, KeyParameter::KeyParameter,
- KeyParameterArray::KeyParameterArray, KeyParameterValue::KeyParameterValue,
- KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag,
+ KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ SecurityLevel::SecurityLevel, Tag::Tag,
};
use android_hardware_security_keymint::binder::{self, Strong};
use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
@@ -71,7 +71,8 @@
// TODO: If I only need the key itself, don't return the other things.
fn generate_key(legacy: &dyn IKeyMintDevice, kps: Vec<KeyParameter>) -> KeyCreationResult {
- let creation_result = legacy.generateKey(&kps).expect("Failed to generate key");
+ let creation_result =
+ legacy.generateKey(&kps, None /* attest_key */).expect("Failed to generate key");
assert_ne!(creation_result.keyBlob.len(), 0);
creation_result
}
@@ -163,7 +164,8 @@
}];
let kf = KeyFormat::RAW;
let kd = [0; 16];
- let creation_result = legacy.importKey(&kps, kf, &kd).expect("Failed to import key");
+ let creation_result =
+ legacy.importKey(&kps, kf, &kd, None /* attest_key */).expect("Failed to import key");
assert_ne!(creation_result.keyBlob.len(), 0);
assert_eq!(creation_result.certificateChain.len(), 0);
}
@@ -281,49 +283,67 @@
let begin_result = begin(legacy.as_ref(), &blob, KeyPurpose::ENCRYPT, None);
let operation = begin_result.operation.unwrap();
- let params = KeyParameterArray {
- params: vec![KeyParameter {
- tag: Tag::ASSOCIATED_DATA,
- value: KeyParameterValue::Blob(b"foobar".to_vec()),
- }],
- };
+
+ let update_aad_result = operation.updateAad(
+ &b"foobar".to_vec(),
+ None, /* authToken */
+ None, /* timestampToken */
+ );
+ assert!(update_aad_result.is_ok(), "{:?}", update_aad_result);
+
let message = [42; 128];
- let mut out_params = None;
- let result =
- operation.finish(Some(¶ms), Some(&message), None, None, None, &mut out_params);
+ let result = operation.finish(
+ Some(&message),
+ None, /* signature */
+ None, /* authToken */
+ None, /* timestampToken */
+ None, /* confirmationToken */
+ );
assert!(result.is_ok(), "{:?}", result);
let ciphertext = result.unwrap();
assert!(!ciphertext.is_empty());
- assert!(out_params.is_some());
let begin_result =
begin(legacy.as_ref(), &blob, KeyPurpose::DECRYPT, Some(begin_result.params));
+
let operation = begin_result.operation.unwrap();
- let mut out_params = None;
- let mut output = None;
+
+ let update_aad_result = operation.updateAad(
+ &b"foobar".to_vec(),
+ None, /* authToken */
+ None, /* timestampToken */
+ );
+ assert!(update_aad_result.is_ok(), "{:?}", update_aad_result);
+
let result = operation.update(
- Some(¶ms),
- Some(&ciphertext),
- None,
- None,
- &mut out_params,
- &mut output,
+ &ciphertext,
+ None, /* authToken */
+ None, /* timestampToken */
);
assert!(result.is_ok(), "{:?}", result);
- assert_eq!(result.unwrap(), message.len() as i32);
- assert!(output.is_some());
- assert_eq!(output.unwrap().data, message.to_vec());
- let result = operation.finish(Some(¶ms), None, None, None, None, &mut out_params);
+ assert_eq!(result.unwrap(), message);
+ let result = operation.finish(
+ None, /* input */
+ None, /* signature */
+ None, /* authToken */
+ None, /* timestampToken */
+ None, /* confirmationToken */
+ );
assert!(result.is_ok(), "{:?}", result);
- assert!(out_params.is_some());
}
#[test]
fn test_secure_clock() {
add_keymint_device_service();
let compat_service: binder::Strong<dyn IKeystoreCompatService> =
- binder::get_interface(COMPAT_NAME).unwrap();
- let secure_clock = compat_service.getSecureClock().unwrap();
+ match binder::get_interface(COMPAT_NAME) {
+ Ok(cs) => cs,
+ _ => return,
+ };
+ let secure_clock = match compat_service.getSecureClock() {
+ Ok(sc) => sc,
+ _ => return,
+ };
let challenge = 42;
let result = secure_clock.generateTimeStamp(challenge);
@@ -337,9 +357,15 @@
fn test_shared_secret() {
add_keymint_device_service();
let compat_service: binder::Strong<dyn IKeystoreCompatService> =
- binder::get_interface(COMPAT_NAME).unwrap();
- let shared_secret =
- compat_service.getSharedSecret(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ match binder::get_interface(COMPAT_NAME) {
+ Ok(cs) => cs,
+ _ => return,
+ };
+ let shared_secret = match compat_service.getSharedSecret(SecurityLevel::TRUSTED_ENVIRONMENT)
+ {
+ Ok(ss) => ss,
+ _ => return,
+ };
let result = shared_secret.getSharedSecretParameters();
assert!(result.is_ok(), "{:?}", result);
diff --git a/keystore2/src/km_compat/slot_test.cpp b/keystore2/src/km_compat/slot_test.cpp
index 0859ddf..43f3bc6 100644
--- a/keystore2/src/km_compat/slot_test.cpp
+++ b/keystore2/src/km_compat/slot_test.cpp
@@ -24,7 +24,6 @@
using ::aidl::android::hardware::security::keymint::Algorithm;
using ::aidl::android::hardware::security::keymint::BlockMode;
-using ::aidl::android::hardware::security::keymint::ByteArray;
using ::aidl::android::hardware::security::keymint::Certificate;
using ::aidl::android::hardware::security::keymint::Digest;
using ::aidl::android::hardware::security::keymint::ErrorCode;
@@ -47,7 +46,7 @@
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::DECRYPT),
});
KeyCreationResult creationResult;
- auto status = device->generateKey(keyParams, &creationResult);
+ auto status = device->generateKey(keyParams, std::nullopt /* attest_key */, &creationResult);
if (!status.isOk()) {
return {};
}
@@ -100,18 +99,19 @@
// Calling finish should free up a slot.
auto last = operations.back();
operations.pop_back();
- std::optional<KeyParameterArray> kpa;
std::vector<uint8_t> byteVec;
- auto status = last->finish(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
- &kpa, &byteVec);
+ auto status = last->finish(std::nullopt /* input */, std::nullopt /* signature */,
+ std::nullopt /* authToken */, std::nullopt /* timestampToken */,
+ std::nullopt /* confirmationToken */, &byteVec);
ASSERT_TRUE(status.isOk());
result = begin(device, true);
ASSERT_TRUE(std::holds_alternative<BeginResult>(result));
operations.push_back(std::get<BeginResult>(result).operation);
// Calling finish and abort on an already-finished operation should not free up another slot.
- status = last->finish(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
- &kpa, &byteVec);
+ status = last->finish(std::nullopt /* input */, std::nullopt /* signature */,
+ std::nullopt /* authToken */, std::nullopt /* timestampToken */,
+ std::nullopt /* confirmationToken */, &byteVec);
ASSERT_TRUE(!status.isOk());
status = last->abort();
ASSERT_TRUE(!status.isOk());
@@ -130,8 +130,9 @@
operations.push_back(std::get<BeginResult>(result).operation);
// Calling finish and abort on an already-aborted operation should not free up another slot.
- status = last->finish(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
- &kpa, &byteVec);
+ status = last->finish(std::nullopt /* input */, std::nullopt /* signature */,
+ std::nullopt /* authToken */, std::nullopt /* timestampToken */,
+ std::nullopt /* confirmationToken */, &byteVec);
ASSERT_TRUE(!status.isOk());
status = last->abort();
ASSERT_TRUE(!status.isOk());
@@ -140,23 +141,23 @@
ASSERT_EQ(std::get<ScopedAStatus>(result).getServiceSpecificError(),
static_cast<int32_t>(ErrorCode::TOO_MANY_OPERATIONS));
- // Generating a certificate with signWith also uses a slot.
+ // Generating a certificate with signWith uses a slot but falls back to not using one.
auto kps = std::vector<KeyParameter>({
KMV1::makeKeyParameter(KMV1::TAG_ALGORITHM, Algorithm::RSA),
KMV1::makeKeyParameter(KMV1::TAG_KEY_SIZE, 2048),
KMV1::makeKeyParameter(KMV1::TAG_RSA_PUBLIC_EXPONENT, 65537),
KMV1::makeKeyParameter(KMV1::TAG_DIGEST, Digest::SHA_2_256),
KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::SIGN),
+ KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_BEFORE, 0),
+ KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_AFTER, 253402300799000),
KMV1::makeKeyParameter(KMV1::TAG_NO_AUTH_REQUIRED, true),
});
KeyCreationResult creationResult;
- status = device->generateKey(kps, &creationResult);
- ASSERT_TRUE(!status.isOk());
- ASSERT_EQ(status.getServiceSpecificError(),
- static_cast<int32_t>(ErrorCode::TOO_MANY_OPERATIONS));
+ status = device->generateKey(kps, std::nullopt /* attest_key */, &creationResult);
+ ASSERT_TRUE(status.isOk());
// But generating a certificate with signCert does not use a slot.
kps.pop_back();
- status = device->generateKey(kps, &creationResult);
+ status = device->generateKey(kps, std::nullopt /* attest_key */, &creationResult);
ASSERT_TRUE(status.isOk());
// Destructing operations should free up their slots.
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index 230a82c..3fc77b7 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -17,7 +17,6 @@
//! This module implements methods to load legacy keystore key blob files.
use crate::{
- database::KeyMetaData,
error::{Error as KsError, ResponseCode},
key_parameter::{KeyParameter, KeyParameterValue},
super_key::SuperKeyManager,
@@ -28,8 +27,12 @@
};
use anyhow::{Context, Result};
use keystore2_crypto::{aes_gcm_decrypt, derive_key_from_password, ZVec};
-use std::io::{ErrorKind, Read};
+use std::collections::{HashMap, HashSet};
use std::{convert::TryInto, fs::File, path::Path, path::PathBuf};
+use std::{
+ fs,
+ io::{ErrorKind, Read, Result as IoResult},
+};
const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3;
@@ -231,6 +234,7 @@
pub fn new(path: &Path) -> Self {
Self { path: path.to_owned() }
}
+
/// Encodes an alias string as ascii character sequence in the range
/// ['+' .. '.'] and ['0' .. '~'].
/// Bytes with values in the range ['0' .. '~'] are represented as they are.
@@ -587,7 +591,7 @@
let sw_list = Self::read_key_parameters(&mut stream)
.context("In read_characteristics_file.")?
.into_iter()
- .map(|value| KeyParameter::new(value, SecurityLevel::SOFTWARE));
+ .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
}
@@ -600,7 +604,7 @@
// used this for user installed certificates without private key material.
fn read_km_blob_file(&self, uid: u32, alias: &str) -> Result<Option<(Blob, String)>> {
- let mut iter = ["USRPKEY", "USERSKEY"].iter();
+ let mut iter = ["USRPKEY", "USRSKEY"].iter();
let (blob, prefix) = loop {
if let Some(prefix) = iter.next() {
@@ -619,7 +623,7 @@
}
fn read_generic_blob(path: &Path) -> Result<Option<Blob>> {
- let mut file = match File::open(path) {
+ let mut file = match Self::with_retry_interrupted(|| File::open(path)) {
Ok(file) => file,
Err(e) => match e.kind() {
ErrorKind::NotFound => return Ok(None),
@@ -630,50 +634,339 @@
Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
}
- /// This function constructs the blob file name which has the form:
- /// user_<android user id>/<uid>_<alias>.
- fn make_blob_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
+ /// Read a legacy vpn profile blob.
+ pub fn read_vpn_profile(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ let path = match self.make_vpn_profile_filename(uid, alias) {
+ Some(path) => path,
+ None => return Ok(None),
+ };
+
+ let blob =
+ Self::read_generic_blob(&path).context("In read_vpn_profile: Failed to read blob.")?;
+
+ Ok(blob.and_then(|blob| match blob.value {
+ BlobValue::Generic(blob) => Some(blob),
+ _ => {
+ log::info!("Unexpected vpn profile blob type. Ignoring");
+ None
+ }
+ }))
+ }
+
+ /// Remove a vpn profile by the name alias with owner uid.
+ pub fn remove_vpn_profile(&self, uid: u32, alias: &str) -> Result<()> {
+ let path = match self.make_vpn_profile_filename(uid, alias) {
+ Some(path) => path,
+ None => return Ok(()),
+ };
+
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ ErrorKind::NotFound => return Ok(()),
+ _ => return Err(e).context("In remove_vpn_profile."),
+ }
+ }
+
+ let user_id = uid_to_android_user(uid);
+ self.remove_user_dir_if_empty(user_id)
+ .context("In remove_vpn_profile: Trying to remove empty user dir.")
+ }
+
+ fn is_vpn_profile(encoded_alias: &str) -> bool {
+ // We can check the encoded alias because the prefixes we are interested
+ // in are all in the printable range that don't get mangled.
+ encoded_alias.starts_with("VPN_")
+ || encoded_alias.starts_with("PLATFORM_VPN_")
+ || encoded_alias == "LOCKDOWN_VPN"
+ }
+
+ /// List all profiles belonging to the given uid.
+ pub fn list_vpn_profiles(&self, uid: u32) -> Result<Vec<String>> {
let mut path = self.path.clone();
let user_id = uid_to_android_user(uid);
- let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
path.push(format!("user_{}", user_id));
+ let uid_str = uid.to_string();
+ let dir =
+ Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
+ format!("In list_vpn_profiles: Failed to open legacy blob database. {:?}", path)
+ })?;
+ let mut result: Vec<String> = Vec::new();
+ for entry in dir {
+ let file_name =
+ entry.context("In list_vpn_profiles: Trying to access dir entry")?.file_name();
+ if let Some(f) = file_name.to_str() {
+ let encoded_alias = &f[uid_str.len() + 1..];
+ if f.starts_with(&uid_str) && Self::is_vpn_profile(encoded_alias) {
+ result.push(
+ Self::decode_alias(encoded_alias)
+ .context("In list_vpn_profiles: Trying to decode alias.")?,
+ )
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ /// This function constructs the vpn_profile file name which has the form:
+ /// user_<android user id>/<uid>_<alias>.
+ fn make_vpn_profile_filename(&self, uid: u32, alias: &str) -> Option<PathBuf> {
+ // legacy vpn entries must start with VPN_ or PLATFORM_VPN_ or are literally called
+ // LOCKDOWN_VPN.
+ if !Self::is_vpn_profile(alias) {
+ return None;
+ }
+
+ let mut path = self.path.clone();
+ let user_id = uid_to_android_user(uid);
+ let encoded_alias = Self::encode_alias(alias);
+ path.push(format!("user_{}", user_id));
+ path.push(format!("{}_{}", uid, encoded_alias));
+ Some(path)
+ }
+
+ /// This function constructs the blob file name which has the form:
+ /// user_<android user id>/<uid>_<prefix>_<alias>.
+ fn make_blob_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
+ let user_id = uid_to_android_user(uid);
+ let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
+ let mut path = self.make_user_path_name(user_id);
path.push(format!("{}_{}", uid, encoded_alias));
path
}
/// This function constructs the characteristics file name which has the form:
- /// user_<android user id>/.<uid>_chr_<alias>.
+ /// user_<android user id>/.<uid>_chr_<prefix>_<alias>.
fn make_chr_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
- let mut path = self.path.clone();
let user_id = uid_to_android_user(uid);
let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
- path.push(format!("user_{}", user_id));
+ let mut path = self.make_user_path_name(user_id);
path.push(format!(".{}_chr_{}", uid, encoded_alias));
path
}
- fn load_by_uid_alias(
+ fn make_super_key_filename(&self, user_id: u32) -> PathBuf {
+ let mut path = self.make_user_path_name(user_id);
+ path.push(".masterkey");
+ path
+ }
+
+ fn make_user_path_name(&self, user_id: u32) -> PathBuf {
+ let mut path = self.path.clone();
+ path.push(&format!("user_{}", user_id));
+ path
+ }
+
+ /// Returns if the legacy blob database is empty, i.e., there are no entries matching "user_*"
+ /// in the database dir.
+ pub fn is_empty(&self) -> Result<bool> {
+ let dir = Self::with_retry_interrupted(|| fs::read_dir(self.path.as_path()))
+ .context("In is_empty: Failed to open legacy blob database.")?;
+ for entry in dir {
+ if (*entry.context("In is_empty: Trying to access dir entry")?.file_name())
+ .to_str()
+ .map_or(false, |f| f.starts_with("user_"))
+ {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+ }
+
+ /// Returns if the legacy blob database is empty for a given user, i.e., there are no entries
+ /// matching "user_*" in the database dir.
+ pub fn is_empty_user(&self, user_id: u32) -> Result<bool> {
+ let mut user_path = self.path.clone();
+ user_path.push(format!("user_{}", user_id));
+ if !user_path.as_path().is_dir() {
+ return Ok(true);
+ }
+ Ok(Self::with_retry_interrupted(|| user_path.read_dir())
+ .context("In is_empty_user: Failed to open legacy user dir.")?
+ .next()
+ .is_none())
+ }
+
+ fn extract_alias(encoded_alias: &str) -> Option<String> {
+ // We can check the encoded alias because the prefixes we are interested
+ // in are all in the printable range that don't get mangled.
+ for prefix in &["USRPKEY_", "USRSKEY_", "USRCERT_", "CACERT_"] {
+ if let Some(alias) = encoded_alias.strip_prefix(prefix) {
+ return Self::decode_alias(&alias).ok();
+ }
+ }
+ None
+ }
+
+ /// List all entries for a given user. The strings are unchanged file names, i.e.,
+ /// encoded with UID prefix.
+ fn list_user(&self, user_id: u32) -> Result<Vec<String>> {
+ let path = self.make_user_path_name(user_id);
+ let dir =
+ Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
+ format!("In list_user: Failed to open legacy blob database. {:?}", path)
+ })?;
+ let mut result: Vec<String> = Vec::new();
+ for entry in dir {
+ let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name();
+ if let Some(f) = file_name.to_str() {
+ result.push(f.to_string())
+ }
+ }
+ Ok(result)
+ }
+
+ /// List all keystore entries belonging to the given user. Returns a map of UIDs
+ /// to sets of decoded aliases.
+ pub fn list_keystore_entries_for_user(
+ &self,
+ user_id: u32,
+ ) -> Result<HashMap<u32, HashSet<String>>> {
+ let user_entries = self
+ .list_user(user_id)
+ .context("In list_keystore_entries_for_user: Trying to list user.")?;
+
+ let result =
+ user_entries.into_iter().fold(HashMap::<u32, HashSet<String>>::new(), |mut acc, v| {
+ if let Some(sep_pos) = v.find('_') {
+ if let Ok(uid) = v[0..sep_pos].parse::<u32>() {
+ if let Some(alias) = Self::extract_alias(&v[sep_pos + 1..]) {
+ let entry = acc.entry(uid).or_default();
+ entry.insert(alias);
+ }
+ }
+ }
+ acc
+ });
+ Ok(result)
+ }
+
+ /// List all keystore entries belonging to the given uid.
+ pub fn list_keystore_entries_for_uid(&self, uid: u32) -> Result<Vec<String>> {
+ let user_id = uid_to_android_user(uid);
+
+ let user_entries = self
+ .list_user(user_id)
+ .context("In list_keystore_entries_for_uid: Trying to list user.")?;
+
+ let uid_str = format!("{}_", uid);
+
+ let mut result: Vec<String> = user_entries
+ .into_iter()
+ .filter_map(|v| {
+ if !v.starts_with(&uid_str) {
+ return None;
+ }
+ let encoded_alias = &v[uid_str.len()..];
+ Self::extract_alias(encoded_alias)
+ })
+ .collect();
+
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
+ }
+
+ fn with_retry_interrupted<F, T>(f: F) -> IoResult<T>
+ where
+ F: Fn() -> IoResult<T>,
+ {
+ loop {
+ match f() {
+ Ok(v) => return Ok(v),
+ Err(e) => match e.kind() {
+ ErrorKind::Interrupted => continue,
+ _ => return Err(e),
+ },
+ }
+ }
+ }
+
+ /// Deletes a keystore entry. Also removes the user_<uid> directory on the
+ /// last migration.
+ pub fn remove_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
+ let mut something_was_deleted = false;
+ let prefixes = ["USRPKEY", "USRSKEY"];
+ for prefix in &prefixes {
+ let path = self.make_blob_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ // Only a subset of keys are expected.
+ ErrorKind::NotFound => continue,
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ }
+ let path = self.make_chr_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ ErrorKind::NotFound => {
+ log::info!("No characteristics file found for legacy key blob.")
+ }
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ }
+ something_was_deleted = true;
+ // Only one of USRPKEY and USRSKEY can be present. So we can end the loop
+ // if we reach this point.
+ break;
+ }
+
+ let prefixes = ["USRCERT", "CACERT"];
+ for prefix in &prefixes {
+ let path = self.make_blob_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ // USRCERT and CACERT are optional either or both may or may not be present.
+ ErrorKind::NotFound => continue,
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ something_was_deleted = true;
+ }
+ }
+
+ if something_was_deleted {
+ let user_id = uid_to_android_user(uid);
+ self.remove_user_dir_if_empty(user_id)
+ .context("In remove_keystore_entry: Trying to remove empty user dir.")?;
+ }
+
+ Ok(something_was_deleted)
+ }
+
+ fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> {
+ if self
+ .is_empty_user(user_id)
+ .context("In remove_user_dir_if_empty: Trying to check for empty user dir.")?
+ {
+ let user_path = self.make_user_path_name(user_id);
+ Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
+ }
+ Ok(())
+ }
+
+ /// Load a legacy key blob entry by uid and alias.
+ pub fn load_by_uid_alias(
&self,
uid: u32,
alias: &str,
- key_manager: &SuperKeyManager,
- ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>, KeyMetaData)>
- {
- let metadata = KeyMetaData::new();
-
+ key_manager: Option<&SuperKeyManager>,
+ ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?;
let km_blob = match km_blob {
Some((km_blob, prefix)) => {
- let km_blob =
- match km_blob {
- Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
- // Unwrap the key blob if required.
- Blob { flags, value: BlobValue::Encrypted { iv, tag, data } } => {
+ let km_blob = match km_blob {
+ Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
+ // Unwrap the key blob if required and if we have key_manager.
+ Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => {
+ if let Some(key_manager) = key_manager {
let decrypted = match key_manager
.get_per_boot_key_by_user_id(uid_to_android_user(uid))
{
- Some(key) => aes_gcm_decrypt(&data, &iv, &tag, &key).context(
+ Some(key) => aes_gcm_decrypt(data, iv, tag, &(key.get_key()))
+ .context(
"In load_by_uid_alias: while trying to decrypt legacy blob.",
)?,
None => {
@@ -687,11 +980,16 @@
}
};
Blob { flags, value: BlobValue::Decrypted(decrypted) }
+ } else {
+ km_blob
}
- _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
+ }
+ _ => {
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
"In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
- ),
- };
+ )
+ }
+ };
let hw_sec_level = match km_blob.is_strongbox() {
true => SecurityLevel::STRONGBOX,
@@ -729,14 +1027,17 @@
}
};
- Ok((km_blob, user_cert, ca_cert, metadata))
+ Ok((km_blob, user_cert, ca_cert))
+ }
+
+ /// Returns true if the given user has a super key.
+ pub fn has_super_key(&self, user_id: u32) -> bool {
+ self.make_super_key_filename(user_id).is_file()
}
/// Load and decrypt legacy super key blob.
pub fn load_super_key(&self, user_id: u32, pw: &[u8]) -> Result<Option<ZVec>> {
- let mut path = self.path.clone();
- path.push(&format!("user_{}", user_id));
- path.push(".masterkey");
+ let path = self.make_super_key_filename(user_id);
let blob = Self::read_generic_blob(&path)
.context("In load_super_key: While loading super key.")?;
@@ -763,6 +1064,18 @@
Ok(blob)
}
+
+ /// Removes the super key for the given user from the legacy database.
+ /// If this was the last entry in the user's database, this function removes
+ /// the user_<uid> directory as well.
+ pub fn remove_super_key(&self, user_id: u32) {
+ let path = self.make_super_key_filename(user_id);
+ Self::with_retry_interrupted(|| fs::remove_file(path.as_path())).ok();
+ if self.is_empty_user(user_id).ok().unwrap_or(false) {
+ let path = self.make_user_path_name(user_id);
+ Self::with_retry_interrupted(|| fs::remove_dir(path.as_path())).ok();
+ }
+ }
}
#[cfg(test)]
@@ -897,6 +1210,37 @@
}
#[test]
+ fn test_is_empty() {
+ let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
+
+ let _db = crate::database::KeystoreDB::new(temp_dir.path(), None)
+ .expect("Failed to open database.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_0"))
+ .expect("Failed to remove user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_10"))
+ .expect("Failed to remove user_10.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
+ }
+
+ #[test]
fn test_legacy_blobs() -> anyhow::Result<()> {
let temp_dir = TempDir::new("legacy_blob_test")?;
std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
@@ -938,32 +1282,32 @@
)?;
let key_manager = crate::super_key::SuperKeyManager::new();
- let mut db = crate::database::KeystoreDB::new(temp_dir.path())?;
+ let mut db = crate::database::KeystoreDB::new(temp_dir.path(), None)?;
let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
assert_eq!(
legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", &key_manager)
+ .load_by_uid_alias(10223, "authbound", Some(&key_manager))
.unwrap_err()
.root_cause()
.downcast_ref::<error::Error>(),
Some(&error::Error::Rc(ResponseCode::LOCKED))
);
- key_manager.unlock_user_key(0, PASSWORD, &mut db, &legacy_blob_loader)?;
+ key_manager.unlock_user_key(&mut db, 0, PASSWORD, &legacy_blob_loader)?;
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain), _kp) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &key_manager)?
+ if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
{
assert_eq!(flags, 4);
- assert_eq!(value, BlobValue::Decrypted(DECRYPTED_USRPKEY_AUTHBOUND.try_into()?));
+ //assert_eq!(value, BlobValue::Encrypted(..));
assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
} else {
panic!("");
}
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain), _kp) =
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &key_manager)?
+ if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
{
assert_eq!(flags, 0);
assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
@@ -973,6 +1317,33 @@
panic!("");
}
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+ legacy_blob_loader
+ .remove_keystore_entry(10223, "non_authbound")
+ .expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ );
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty()?);
+ assert!(!legacy_blob_loader.is_empty_user(0)?);
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1)?);
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0)?);
+ assert!(legacy_blob_loader.is_empty()?);
+
Ok(())
}
}
diff --git a/keystore2/src/legacy_migrator.rs b/keystore2/src/legacy_migrator.rs
new file mode 100644
index 0000000..1ae8719
--- /dev/null
+++ b/keystore2/src/legacy_migrator.rs
@@ -0,0 +1,713 @@
+// Copyright 2021, 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.
+
+//! This module acts as a bridge between the legacy key database and the keystore2 database.
+
+use crate::database::{
+ BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData, KeyMetaEntry,
+ KeystoreDB, Uuid, KEYSTORE_UUID,
+};
+use crate::error::Error;
+use crate::key_parameter::KeyParameterValue;
+use crate::legacy_blob::BlobValue;
+use crate::utils::uid_to_android_user;
+use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+use anyhow::{Context, Result};
+use core::ops::Deref;
+use keystore2_crypto::ZVec;
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use std::sync::atomic::{AtomicU8, Ordering};
+use std::sync::mpsc::channel;
+use std::sync::{Arc, Mutex};
+
+/// Represents LegacyMigrator.
+pub struct LegacyMigrator {
+ async_task: Arc<AsyncTask>,
+ initializer: Mutex<
+ Option<
+ Box<
+ dyn FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ >,
+ >,
+ >,
+ /// This atomic is used for cheap interior mutability. It is intended to prevent
+ /// expensive calls into the legacy migrator when the legacy database is empty.
+ /// When transitioning from READY to EMPTY, spurious calls may occur for a brief period
+ /// of time. This is tolerable in favor of the common case.
+ state: AtomicU8,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct RecentMigration {
+ uid: u32,
+ alias: String,
+}
+
+impl RecentMigration {
+ fn new(uid: u32, alias: String) -> Self {
+ Self { uid, alias }
+ }
+}
+
+enum BulkDeleteRequest {
+ Uid(u32),
+ User(u32),
+}
+
+struct LegacyMigratorState {
+ recently_migrated: HashSet<RecentMigration>,
+ recently_migrated_super_key: HashSet<u32>,
+ legacy_loader: Arc<LegacyBlobLoader>,
+ sec_level_to_km_uuid: HashMap<SecurityLevel, Uuid>,
+ db: KeystoreDB,
+}
+
+impl LegacyMigrator {
+ const WIFI_NAMESPACE: i64 = 102;
+ const AID_WIFI: u32 = 1010;
+
+ const STATE_UNINITIALIZED: u8 = 0;
+ const STATE_READY: u8 = 1;
+ const STATE_EMPTY: u8 = 2;
+
+ /// Constructs a new LegacyMigrator using the given AsyncTask object as migration
+ /// worker.
+ pub fn new(async_task: Arc<AsyncTask>) -> Self {
+ Self {
+ async_task,
+ initializer: Default::default(),
+ state: AtomicU8::new(Self::STATE_UNINITIALIZED),
+ }
+ }
+
+ /// The legacy migrator must be initialized deferred, because keystore starts very early.
+ /// At this time the data partition may not be mounted. So we cannot open database connections
+ /// until we get actual key load requests. This sets the function that the legacy loader
+ /// uses to connect to the database.
+ pub fn set_init<F>(&self, f_init: F) -> Result<()>
+ where
+ F: FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ {
+ let mut initializer = self.initializer.lock().expect("Failed to lock initializer.");
+
+ // If we are not uninitialized we have no business setting the initializer.
+ if self.state.load(Ordering::Relaxed) != Self::STATE_UNINITIALIZED {
+ return Ok(());
+ }
+
+ // Only set the initializer if it hasn't been set before.
+ if initializer.is_none() {
+ *initializer = Some(Box::new(f_init))
+ }
+
+ Ok(())
+ }
+
+ /// This function is called by the migration requestor to check if it is worth
+ /// making a migration request. It also transitions the state from UNINITIALIZED
+ /// to READY or EMPTY on first use. The deferred initialization is necessary, because
+ /// Keystore 2.0 runs early during boot, where data may not yet be mounted.
+ /// Returns Ok(STATE_READY) if a migration request is worth undertaking and
+ /// Ok(STATE_EMPTY) if the database is empty. An error is returned if the loader
+ /// was not initialized and cannot be initialized.
+ fn check_state(&self) -> Result<u8> {
+ let mut first_try = true;
+ loop {
+ match (self.state.load(Ordering::Relaxed), first_try) {
+ (Self::STATE_EMPTY, _) => {
+ return Ok(Self::STATE_EMPTY);
+ }
+ (Self::STATE_UNINITIALIZED, true) => {
+ // If we find the legacy loader uninitialized, we grab the initializer lock,
+ // check if the legacy database is empty, and if not, schedule an initialization
+ // request. Coming out of the initializer lock, the state is either EMPTY or
+ // READY.
+ let mut initializer = self.initializer.lock().unwrap();
+
+ if let Some(initializer) = initializer.take() {
+ let (db, sec_level_to_km_uuid, legacy_loader) = (initializer)();
+
+ if legacy_loader.is_empty().context(
+ "In check_state: Trying to check if the legacy database is empty.",
+ )? {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed);
+ return Ok(Self::STATE_EMPTY);
+ }
+
+ self.async_task.queue_hi(move |shelf| {
+ shelf.get_or_put_with(|| LegacyMigratorState {
+ recently_migrated: Default::default(),
+ recently_migrated_super_key: Default::default(),
+ legacy_loader,
+ sec_level_to_km_uuid,
+ db,
+ });
+ });
+
+ // It is safe to set this here even though the async task may not yet have
+ // run because any thread observing this will not be able to schedule a
+ // task that can run before the initialization.
+ // Also we can only transition out of this state while having the
+ // initializer lock and having found an initializer.
+ self.state.store(Self::STATE_READY, Ordering::Relaxed);
+ return Ok(Self::STATE_READY);
+ } else {
+ // There is a chance that we just lost the race from state.load() to
+ // grabbing the initializer mutex. If that is the case the state must
+ // be EMPTY or READY after coming out of the lock. So we can give it
+ // one more try.
+ first_try = false;
+ continue;
+ }
+ }
+ (Self::STATE_UNINITIALIZED, false) => {
+ // Okay, tough luck. The legacy loader was really completely uninitialized.
+ return Err(Error::sys()).context(
+ "In check_state: Legacy loader should not be called uninitialized.",
+ );
+ }
+ (Self::STATE_READY, _) => return Ok(Self::STATE_READY),
+ (s, _) => panic!("Unknown legacy migrator state. {} ", s),
+ }
+ }
+ }
+
+ /// List all aliases for uid in the legacy database.
+ pub fn list_uid(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let uid = match (domain, namespace) {
+ (Domain::APP, namespace) => namespace as u32,
+ (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
+ _ => return Ok(Vec::new()),
+ };
+ self.do_serialized(move |state| state.list_uid(uid)).unwrap_or_else(|| Ok(Vec::new())).map(
+ |v| {
+ v.into_iter()
+ .map(|alias| KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(alias),
+ blob: None,
+ })
+ .collect()
+ },
+ )
+ }
+
+ /// Sends the given closure to the migrator thread for execution after calling check_state.
+ /// Returns None if the database was empty and the request was not executed.
+ /// Otherwise returns Some with the result produced by the migration request.
+ /// The loader state may transition to STATE_EMPTY during the execution of this function.
+ fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Option<Result<T>>
+ where
+ F: FnOnce(&mut LegacyMigratorState) -> Result<T> + Send + 'static,
+ {
+ // Short circuit if the database is empty or not initialized (error case).
+ match self.check_state().context("In do_serialized: Checking state.") {
+ Ok(LegacyMigrator::STATE_EMPTY) => return None,
+ Ok(LegacyMigrator::STATE_READY) => {}
+ Err(e) => return Some(Err(e)),
+ Ok(s) => panic!("Unknown legacy migrator state. {} ", s),
+ }
+
+ // We have established that there may be a key in the legacy database.
+ // Now we schedule a migration request.
+ let (sender, receiver) = channel();
+ self.async_task.queue_hi(move |shelf| {
+ // Get the migrator state from the shelf.
+ // There may not be a state. This can happen if this migration request was scheduled
+ // before a previous request established that the legacy database was empty
+ // and removed the state from the shelf. Since we know now that the database
+ // is empty, we can return None here.
+ let (new_state, result) = if let Some(legacy_migrator_state) =
+ shelf.get_downcast_mut::<LegacyMigratorState>()
+ {
+ let result = f(legacy_migrator_state);
+ (legacy_migrator_state.check_empty(), Some(result))
+ } else {
+ (Self::STATE_EMPTY, None)
+ };
+
+ // If the migration request determined that the database is now empty, we discard
+ // the state from the shelf to free up the resources we won't need any longer.
+ if result.is_some() && new_state == Self::STATE_EMPTY {
+ shelf.remove_downcast_ref::<LegacyMigratorState>();
+ }
+
+ // Send the result to the requester.
+ if let Err(e) = sender.send((new_state, result)) {
+ log::error!("In do_serialized. Error in sending the result. {:?}", e);
+ }
+ });
+
+ let (new_state, result) = match receiver.recv() {
+ Err(e) => {
+ return Some(Err(e).context("In do_serialized. Failed to receive from the sender."))
+ }
+ Ok(r) => r,
+ };
+
+ // We can only transition to EMPTY but never back.
+ // The migrator never creates any legacy blobs.
+ if new_state == Self::STATE_EMPTY {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed)
+ }
+
+ result
+ }
+
+ /// Runs the key_accessor function and returns its result. If it returns an error and the
+ /// root cause was KEY_NOT_FOUND, tries to migrate a key with the given parameters from
+ /// the legacy database to the new database and runs the key_accessor function again if
+ /// the migration request was successful.
+ pub fn with_try_migrate<F, T>(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ key_accessor: F,
+ ) -> Result<T>
+ where
+ F: Fn() -> Result<T>,
+ {
+ // Access the key and return on success.
+ match key_accessor() {
+ Ok(result) => return Ok(result),
+ Err(e) => match e.root_cause().downcast_ref::<Error>() {
+ Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
+ _ => return Err(e),
+ },
+ }
+
+ // Filter inputs. We can only load legacy app domain keys and some special rules due
+ // to which we migrate keys transparently to an SELINUX domain.
+ let uid = match key {
+ KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => caller_uid,
+ KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => {
+ match *nspace {
+ Self::WIFI_NAMESPACE => Self::AID_WIFI,
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(format!("No legacy keys for namespace {}", nspace))
+ }
+ }
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("No legacy keys for key descriptor.")
+ }
+ };
+
+ let key_clone = key.clone();
+ let result = self
+ .do_serialized(move |migrator_state| migrator_state.check_and_migrate(uid, key_clone));
+
+ if let Some(result) = result {
+ result?;
+ // After successful migration try again.
+ key_accessor()
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
+ }
+ }
+
+ /// Calls key_accessor and returns the result on success. In the case of a KEY_NOT_FOUND error
+ /// this function makes a migration request and on success retries the key_accessor.
+ pub fn with_try_migrate_super_key<F, T>(
+ &self,
+ user_id: u32,
+ pw: &[u8],
+ mut key_accessor: F,
+ ) -> Result<Option<T>>
+ where
+ F: FnMut() -> Result<Option<T>>,
+ {
+ match key_accessor() {
+ Ok(Some(result)) => return Ok(Some(result)),
+ Ok(None) => {}
+ Err(e) => return Err(e),
+ }
+
+ let pw: ZVec = pw
+ .try_into()
+ .context("In with_try_migrate_super_key: copying the password into a zvec.")?;
+ let result = self.do_serialized(move |migrator_state| {
+ migrator_state.check_and_migrate_super_key(user_id, pw)
+ });
+
+ if let Some(result) = result {
+ result?;
+ // After successful migration try again.
+ key_accessor()
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Deletes all keys belonging to the given namespace, migrating them into the database
+ /// for subsequent garbage collection if necessary.
+ pub fn bulk_delete_uid(&self, domain: Domain, nspace: i64) -> Result<()> {
+ let uid = match (domain, nspace) {
+ (Domain::APP, nspace) => nspace as u32,
+ (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
+ // Nothing to do.
+ _ => return Ok(()),
+ };
+
+ let result = self.do_serialized(move |migrator_state| {
+ migrator_state.bulk_delete(BulkDeleteRequest::Uid(uid), false)
+ });
+
+ result.unwrap_or(Ok(()))
+ }
+
+ /// Deletes all keys belonging to the given android user, migrating them into the database
+ /// for subsequent garbage collection if necessary.
+ pub fn bulk_delete_user(
+ &self,
+ user_id: u32,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ let result = self.do_serialized(move |migrator_state| {
+ migrator_state
+ .bulk_delete(BulkDeleteRequest::User(user_id), keep_non_super_encrypted_keys)
+ });
+
+ result.unwrap_or(Ok(()))
+ }
+
+ /// Queries the legacy database for the presence of a super key for the given user.
+ pub fn has_super_key(&self, user_id: u32) -> Result<bool> {
+ let result =
+ self.do_serialized(move |migrator_state| migrator_state.has_super_key(user_id));
+ result.unwrap_or(Ok(false))
+ }
+}
+
+impl LegacyMigratorState {
+ fn get_km_uuid(&self, is_strongbox: bool) -> Result<Uuid> {
+ let sec_level = if is_strongbox {
+ SecurityLevel::STRONGBOX
+ } else {
+ SecurityLevel::TRUSTED_ENVIRONMENT
+ };
+
+ self.sec_level_to_km_uuid.get(&sec_level).copied().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context("In get_km_uuid: No KM instance for blob.")
+ })
+ }
+
+ fn list_uid(&mut self, uid: u32) -> Result<Vec<String>> {
+ self.legacy_loader
+ .list_keystore_entries_for_uid(uid)
+ .context("In list_uid: Trying to list legacy entries.")
+ }
+
+ /// This is a key migration request that can run in the migrator thread. This should
+ /// be passed to do_serialized.
+ fn check_and_migrate(&mut self, uid: u32, mut key: KeyDescriptor) -> Result<()> {
+ let alias = key.alias.clone().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(concat!(
+ "In check_and_migrate: Must be Some because ",
+ "our caller must not have called us otherwise."
+ ))
+ })?;
+
+ if self.recently_migrated.contains(&RecentMigration::new(uid, alias.clone())) {
+ return Ok(());
+ }
+
+ if key.domain == Domain::APP {
+ key.nspace = uid as i64;
+ }
+
+ // If the key is not found in the cache, try to load from the legacy database.
+ let (km_blob_params, user_cert, ca_cert) = self
+ .legacy_loader
+ .load_by_uid_alias(uid, &alias, None)
+ .context("In check_and_migrate: Trying to load legacy blob.")?;
+ let result = match km_blob_params {
+ Some((km_blob, params)) => {
+ let is_strongbox = km_blob.is_strongbox();
+ let (blob, mut blob_metadata) = match km_blob.take_value() {
+ BlobValue::Encrypted { iv, tag, data } => {
+ // Get super key id for user id.
+ let user_id = uid_to_android_user(uid as u32);
+
+ let super_key_id = match self
+ .db
+ .load_super_key(user_id)
+ .context("In check_and_migrate: Failed to load super key")?
+ {
+ Some((_, entry)) => entry.id(),
+ None => {
+ // This might be the first time we access the super key,
+ // and it may not have been migrated. We cannot import
+ // the legacy super_key key now, because we need to reencrypt
+ // it which we cannot do if we are not unlocked, which we are
+ // not because otherwise the key would have been migrated.
+ // We can check though if the key exists. If it does,
+ // we can return Locked. Otherwise, we can delete the
+ // key and return NotFound, because the key will never
+ // be unlocked again.
+ if self.legacy_loader.has_super_key(user_id) {
+ return Err(Error::Rc(ResponseCode::LOCKED)).context(concat!(
+ "In check_and_migrate: Cannot migrate super key of this ",
+ "key while user is locked."
+ ));
+ } else {
+ self.legacy_loader.remove_keystore_entry(uid, &alias).context(
+ concat!(
+ "In check_and_migrate: ",
+ "Trying to remove obsolete key."
+ ),
+ )?;
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Obsolete key.");
+ }
+ }
+ };
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ blob_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ (LegacyBlob::Vec(data), blob_metadata)
+ }
+ BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()),
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Legacy key has unexpected type.")
+ }
+ };
+
+ let km_uuid = self
+ .get_km_uuid(is_strongbox)
+ .context("In check_and_migrate: Trying to get KM UUID")?;
+ blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+
+ let mut metadata = KeyMetaData::new();
+ let creation_date = DateTime::now()
+ .context("In check_and_migrate: Trying to make creation time.")?;
+ metadata.add(KeyMetaEntry::CreationDate(creation_date));
+
+ // Store legacy key in the database.
+ self.db
+ .store_new_key(
+ &key,
+ ¶ms,
+ &(&blob, &blob_metadata),
+ &CertificateInfo::new(user_cert, ca_cert),
+ &metadata,
+ &km_uuid,
+ )
+ .context("In check_and_migrate.")?;
+ Ok(())
+ }
+ None => {
+ if let Some(ca_cert) = ca_cert {
+ self.db
+ .store_new_certificate(&key, &ca_cert, &KEYSTORE_UUID)
+ .context("In check_and_migrate: Failed to insert new certificate.")?;
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Legacy key not found.")
+ }
+ }
+ };
+
+ match result {
+ Ok(()) => {
+ // Add the key to the migrated_keys list.
+ self.recently_migrated.insert(RecentMigration::new(uid, alias.clone()));
+ // Delete legacy key from the file system
+ self.legacy_loader
+ .remove_keystore_entry(uid, &alias)
+ .context("In check_and_migrate: Trying to remove migrated key.")?;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
+
+ fn check_and_migrate_super_key(&mut self, user_id: u32, pw: ZVec) -> Result<()> {
+ if self.recently_migrated_super_key.contains(&user_id) {
+ return Ok(());
+ }
+
+ if let Some(super_key) = self
+ .legacy_loader
+ .load_super_key(user_id, &pw)
+ .context("In check_and_migrate_super_key: Trying to load legacy super key.")?
+ {
+ let (blob, blob_metadata) =
+ crate::super_key::SuperKeyManager::encrypt_with_password(&super_key, &pw)
+ .context("In check_and_migrate_super_key: Trying to encrypt super key.")?;
+
+ self.db.store_super_key(user_id, &(&blob, &blob_metadata)).context(concat!(
+ "In check_and_migrate_super_key: ",
+ "Trying to insert legacy super_key into the database."
+ ))?;
+ self.legacy_loader.remove_super_key(user_id);
+ self.recently_migrated_super_key.insert(user_id);
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate_super_key: No key found do migrate.")
+ }
+ }
+
+ /// Key migrator request to be run by do_serialized.
+ /// See LegacyMigrator::bulk_delete_uid and LegacyMigrator::bulk_delete_user.
+ fn bulk_delete(
+ &mut self,
+ bulk_delete_request: BulkDeleteRequest,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ let (aliases, user_id) = match bulk_delete_request {
+ BulkDeleteRequest::Uid(uid) => (
+ self.legacy_loader
+ .list_keystore_entries_for_uid(uid)
+ .context("In bulk_delete: Trying to get aliases for uid.")
+ .map(|aliases| {
+ let mut h = HashMap::<u32, HashSet<String>>::new();
+ h.insert(uid, aliases.into_iter().collect());
+ h
+ })?,
+ uid_to_android_user(uid),
+ ),
+ BulkDeleteRequest::User(user_id) => (
+ self.legacy_loader
+ .list_keystore_entries_for_user(user_id)
+ .context("In bulk_delete: Trying to get aliases for user_id.")?,
+ user_id,
+ ),
+ };
+
+ let super_key_id = self
+ .db
+ .load_super_key(user_id)
+ .context("In bulk_delete: Failed to load super key")?
+ .map(|(_, entry)| entry.id());
+
+ for (uid, alias) in aliases
+ .into_iter()
+ .map(|(uid, aliases)| aliases.into_iter().map(move |alias| (uid, alias)))
+ .flatten()
+ {
+ let (km_blob_params, _, _) = self
+ .legacy_loader
+ .load_by_uid_alias(uid, &alias, None)
+ .context("In bulk_delete: Trying to load legacy blob.")?;
+
+ // Determine if the key needs special handling to be deleted.
+ let (need_gc, is_super_encrypted) = km_blob_params
+ .as_ref()
+ .map(|(blob, params)| {
+ (
+ params.iter().any(|kp| {
+ KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
+ }),
+ blob.is_encrypted(),
+ )
+ })
+ .unwrap_or((false, false));
+
+ if keep_non_super_encrypted_keys && !is_super_encrypted {
+ continue;
+ }
+
+ if need_gc {
+ let mark_deleted = match km_blob_params
+ .map(|(blob, _)| (blob.is_strongbox(), blob.take_value()))
+ {
+ Some((is_strongbox, BlobValue::Encrypted { iv, tag, data })) => {
+ let mut blob_metadata = BlobMetaData::new();
+ if let (Ok(km_uuid), Some(super_key_id)) =
+ (self.get_km_uuid(is_strongbox), super_key_id)
+ {
+ blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+ blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ blob_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ Some((LegacyBlob::Vec(data), blob_metadata))
+ } else {
+ // Oh well - we tried our best, but if we cannot determine which
+ // KeyMint instance we have to send this blob to, we cannot
+ // do more than delete the key from the file system.
+ // And if we don't know which key wraps this key we cannot
+ // unwrap it for KeyMint either.
+ None
+ }
+ }
+ Some((_, BlobValue::Decrypted(data))) => {
+ Some((LegacyBlob::ZVec(data), BlobMetaData::new()))
+ }
+ _ => None,
+ };
+
+ if let Some((blob, blob_metadata)) = mark_deleted {
+ self.db.set_deleted_blob(&blob, &blob_metadata).context(concat!(
+ "In bulk_delete: Trying to insert deleted ",
+ "blob into the database for garbage collection."
+ ))?;
+ }
+ }
+
+ self.legacy_loader
+ .remove_keystore_entry(uid, &alias)
+ .context("In bulk_delete: Trying to remove migrated key.")?;
+ }
+ Ok(())
+ }
+
+ fn has_super_key(&mut self, user_id: u32) -> Result<bool> {
+ Ok(self.recently_migrated_super_key.contains(&user_id)
+ || self.legacy_loader.has_super_key(user_id))
+ }
+
+ fn check_empty(&self) -> u8 {
+ if self.legacy_loader.is_empty().unwrap_or(false) {
+ LegacyMigrator::STATE_EMPTY
+ } else {
+ LegacyMigrator::STATE_READY
+ }
+ }
+}
+
+enum LegacyBlob {
+ Vec(Vec<u8>),
+ ZVec(ZVec),
+}
+
+impl Deref for LegacyBlob {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Vec(v) => &v,
+ Self::ZVec(v) => &v,
+ }
+ }
+}
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index f9554ea..8fef6cf 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -16,6 +16,7 @@
#![recursion_limit = "256"]
pub mod apc;
+pub mod async_task;
pub mod authorization;
pub mod database;
pub mod enforcements;
@@ -24,14 +25,15 @@
/// Internal Representation of Key Parameter and convenience functions.
pub mod key_parameter;
pub mod legacy_blob;
+pub mod legacy_migrator;
pub mod operation;
pub mod permission;
pub mod remote_provisioning;
pub mod security_level;
pub mod service;
+pub mod user_manager;
pub mod utils;
-mod async_task;
mod db_utils;
mod gc;
mod super_key;
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index c98a76b..4092684 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -126,12 +126,10 @@
//! Either way, we have to revaluate the pruning scores.
use crate::enforcements::AuthInfo;
-use crate::error::{map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode};
+use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode};
use crate::utils::Asp;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- ByteArray::ByteArray, IKeyMintOperation::IKeyMintOperation,
- KeyParameter::KeyParameter as KmParam, KeyParameterArray::KeyParameterArray,
- KeyParameterValue::KeyParameterValue as KmParamValue, Tag::Tag,
+ IKeyMintOperation::IKeyMintOperation,
};
use android_system_keystore2::aidl::android::system::keystore2::{
IKeystoreOperation::BnKeystoreOperation, IKeystoreOperation::IKeystoreOperation,
@@ -325,16 +323,6 @@
Self::check_input_length(aad_input).context("In update_aad")?;
self.touch();
- let params = KeyParameterArray {
- params: vec![KmParam {
- tag: Tag::ASSOCIATED_DATA,
- value: KmParamValue::Blob(aad_input.into()),
- }],
- };
-
- let mut out_params: Option<KeyParameterArray> = None;
- let mut output: Option<ByteArray> = None;
-
let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?;
@@ -347,14 +335,7 @@
self.update_outcome(
&mut *outcome,
- map_km_error(km_op.update(
- Some(¶ms),
- None,
- hat.as_ref(),
- tst.as_ref(),
- &mut out_params,
- &mut output,
- )),
+ map_km_error(km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())),
)
.context("In update_aad: KeyMint::update failed.")?;
@@ -368,8 +349,6 @@
Self::check_input_length(input).context("In update")?;
self.touch();
- let mut out_params: Option<KeyParameterArray> = None;
-
let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?;
@@ -380,39 +359,17 @@
.before_update()
.context("In update: Trying to get auth tokens.")?;
- let mut result: Option<Vec<u8>> = None;
- let mut consumed = 0usize;
- loop {
- let mut output: Option<ByteArray> = None;
- consumed += self
- .update_outcome(
- &mut *outcome,
- map_km_error(km_op.update(
- None,
- Some(&input[consumed..]),
- hat.as_ref(),
- tst.as_ref(),
- &mut out_params,
- &mut output,
- )),
- )
- .context("In update: KeyMint::update failed.")? as usize;
+ let output = self
+ .update_outcome(
+ &mut *outcome,
+ map_km_error(km_op.update(input, hat.as_ref(), tst.as_ref())),
+ )
+ .context("In update: KeyMint::update failed.")?;
- match (output, &mut result) {
- (Some(blob), None) => {
- if !blob.data.is_empty() {
- result = Some(blob.data)
- }
- }
- (Some(mut blob), Some(ref mut result)) => {
- result.append(&mut blob.data);
- }
- (None, _) => {}
- }
-
- if consumed == input.len() {
- return Ok(result);
- }
+ if output.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(output))
}
}
@@ -425,8 +382,6 @@
}
self.touch();
- let mut out_params: Option<KeyParameterArray> = None;
-
let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
self.km_op.get_interface().context("In finish: Failed to get KeyMintOperation.")?;
@@ -437,23 +392,15 @@
.before_finish()
.context("In finish: Trying to get auth tokens.")?;
- let in_params = confirmation_token.map(|token| KeyParameterArray {
- params: vec![KmParam {
- tag: Tag::CONFIRMATION_TOKEN,
- value: KmParamValue::Blob(token),
- }],
- });
-
let output = self
.update_outcome(
&mut *outcome,
map_km_error(km_op.finish(
- in_params.as_ref(),
input,
signature,
hat.as_ref(),
tst.as_ref(),
- &mut out_params,
+ confirmation_token.as_deref(),
)),
)
.context("In finish: KeyMint::finish failed.")?;
@@ -855,11 +802,21 @@
}
fn abort(&self) -> binder::public_api::Result<()> {
- map_or_log_err(
+ map_err_with(
self.with_locked_operation(
|op| op.abort(Outcome::Abort).context("In KeystoreOperation::abort"),
true,
),
+ |e| {
+ match e.root_cause().downcast_ref::<Error>() {
+ // Calling abort on expired operations is something very common.
+ // There is no reason to clutter the log with it. It is never the cause
+ // for a true problem.
+ Some(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {}
+ _ => log::error!("{:?}", e),
+ };
+ e
+ },
Ok,
)
}
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 0f0ca04..576ac3f 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -299,9 +299,15 @@
/// Checked when Keystore 2.0 gets locked.
Lock = 0x10, selinux name: lock;
/// Checked when Keystore 2.0 shall be reset.
- Reset = 0x20, selinux name: reset;
+ Reset = 0x20, selinux name: reset;
/// Checked when Keystore 2.0 shall be unlocked.
- Unlock = 0x40, selinux name: unlock;
+ Unlock = 0x40, selinux name: unlock;
+ /// Checked when user is added or removed.
+ ChangeUser = 0x80, selinux name: change_user;
+ /// Checked when password of the user is changed.
+ ChangePassword = 0x100, selinux name: change_password;
+ /// Checked when a UID is cleared.
+ ClearUID = 0x200, selinux name: clear_uid;
}
);
@@ -659,6 +665,11 @@
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::lock()).is_ok());
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::reset()).is_ok());
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::unlock()).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::change_user()).is_ok());
+ assert!(
+ check_keystore_permission(&system_server_ctx, KeystorePerm::change_password()).is_ok()
+ );
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_uid()).is_ok());
let shell_ctx = Context::new("u:r:shell:s0")?;
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::add_auth()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_ns()));
@@ -667,6 +678,9 @@
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::lock()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::reset()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::unlock()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_user()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_password()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_uid()));
Ok(())
}
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index fe38504..d6cc680 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -19,28 +19,218 @@
//! certificate chains signed by some root authority and stored in a keystore SQLite
//! DB.
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use std::collections::HashMap;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
+ IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, MacedPublicKey::MacedPublicKey,
+ ProtectedData::ProtectedData, SecurityLevel::SecurityLevel, Tag::Tag,
+};
use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning,
IRemoteProvisioning::IRemoteProvisioning,
};
use android_security_remoteprovisioning::binder::Strong;
-use anyhow::Result;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor,
+};
+use anyhow::{Context, Result};
+use keystore2_crypto::parse_subject_from_certificate;
+use std::sync::atomic::{AtomicBool, Ordering};
-use crate::error::map_or_log_err;
-use crate::globals::{get_keymint_device, DB};
+use crate::database::{CertificateChain, KeystoreDB, Uuid};
+use crate::error::{self, map_or_log_err, map_rem_prov_error, Error};
+use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB};
+use crate::utils::Asp;
+/// Contains helper functions to check if remote provisioning is enabled on the system and, if so,
+/// to assign and retrieve attestation keys and certificate chains.
+#[derive(Default)]
+pub struct RemProvState {
+ security_level: SecurityLevel,
+ km_uuid: Uuid,
+ is_hal_present: AtomicBool,
+}
+
+impl RemProvState {
+ /// Creates a RemProvState struct.
+ pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Self {
+ Self { security_level, km_uuid, is_hal_present: AtomicBool::new(true) }
+ }
+
+ /// Checks if remote provisioning is enabled and partially caches the result. On a hybrid system
+ /// remote provisioning can flip from being disabled to enabled depending on responses from the
+ /// server, so unfortunately caching the presence or absence of the HAL is not enough to fully
+ /// make decisions about the state of remote provisioning during runtime.
+ fn check_rem_prov_enabled(&self, db: &mut KeystoreDB) -> Result<bool> {
+ if !self.is_hal_present.load(Ordering::Relaxed)
+ || get_remotely_provisioned_component(&self.security_level).is_err()
+ {
+ self.is_hal_present.store(false, Ordering::Relaxed);
+ return Ok(false);
+ }
+ // To check if remote provisioning is enabled on a system that supports both remote
+ // provisioning and factory provisioned keys, we only need to check if there are any
+ // keys at all generated to indicate if the app has gotten the signal to begin filling
+ // the key pool from the server.
+ let pool_status = db
+ .get_attestation_pool_status(0 /* date */, &self.km_uuid)
+ .context("In check_rem_prov_enabled: failed to get attestation pool status.")?;
+ Ok(pool_status.total != 0)
+ }
+
+ /// Fetches a remote provisioning attestation key and certificate chain inside of the
+ /// returned `CertificateChain` struct if one exists for the given caller_uid. If one has not
+ /// been assigned, this function will assign it. If there are no signed attestation keys
+ /// available to be assigned, it will return the ResponseCode `OUT_OF_KEYS`
+ fn get_rem_prov_attest_key(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ db: &mut KeystoreDB,
+ ) -> Result<Option<CertificateChain>> {
+ match key.domain {
+ Domain::APP => {
+ // Attempt to get an Attestation Key once. If it fails, then the app doesn't
+ // have a valid chain assigned to it. The helper function will return None after
+ // attempting to assign a key. An error will be thrown if the pool is simply out
+ // of usable keys. Then another attempt to fetch the just-assigned key will be
+ // made. If this fails too, something is very wrong.
+ self.get_rem_prov_attest_key_helper(key, caller_uid, db)
+ .context("In get_rem_prov_attest_key: Failed to get a key")?
+ .map_or_else(
+ || self.get_rem_prov_attest_key_helper(key, caller_uid, db),
+ |v| Ok(Some(v)),
+ )
+ .context(concat!(
+ "In get_rem_prov_attest_key: Failed to get a key after",
+ "attempting to assign one."
+ ))?
+ .map_or_else(
+ || {
+ Err(Error::sys()).context(concat!(
+ "In get_rem_prov_attest_key: Attempted to assign a ",
+ "key and failed silently. Something is very wrong."
+ ))
+ },
+ |cert_chain| Ok(Some(cert_chain)),
+ )
+ }
+ _ => Ok(None),
+ }
+ }
+
+ /// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available.
+ fn get_rem_prov_attest_key_helper(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ db: &mut KeystoreDB,
+ ) -> Result<Option<CertificateChain>> {
+ let cert_chain = db
+ .retrieve_attestation_key_and_cert_chain(key.domain, caller_uid as i64, &self.km_uuid)
+ .context("In get_rem_prov_attest_key_helper: Failed to retrieve a key + cert chain")?;
+ match cert_chain {
+ Some(cert_chain) => Ok(Some(cert_chain)),
+ // Either this app needs to be assigned a key, or the pool is empty. An error will
+ // be thrown if there is no key available to assign. This will indicate that the app
+ // should be nudged to provision more keys so keystore can retry.
+ None => {
+ db.assign_attestation_key(key.domain, caller_uid as i64, &self.km_uuid)
+ .context("In get_rem_prov_attest_key_helper: Failed to assign a key")?;
+ Ok(None)
+ }
+ }
+ }
+
+ fn is_asymmetric_key(&self, params: &[KeyParameter]) -> bool {
+ params.iter().any(|kp| {
+ matches!(
+ kp,
+ KeyParameter {
+ tag: Tag::ALGORITHM,
+ value: KeyParameterValue::Algorithm(Algorithm::RSA)
+ } | KeyParameter {
+ tag: Tag::ALGORITHM,
+ value: KeyParameterValue::Algorithm(Algorithm::EC)
+ }
+ )
+ })
+ }
+
+ /// Checks to see (1) if the key in question should be attested to based on the algorithm and
+ /// (2) if remote provisioning is present and enabled on the system. If these conditions are
+ /// met, it makes an attempt to fetch the attestation key assigned to the `caller_uid`.
+ ///
+ /// It returns the ResponseCode `OUT_OF_KEYS` if there is not one key currently assigned to the
+ /// `caller_uid` and there are none available to assign.
+ pub fn get_remote_provisioning_key_and_certs(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ params: &[KeyParameter],
+ db: &mut KeystoreDB,
+ ) -> Result<(Option<AttestationKey>, Option<Certificate>)> {
+ if !self.is_asymmetric_key(params) || !self.check_rem_prov_enabled(db)? {
+ // There is no remote provisioning component for this security level on the
+ // device. Return None so the underlying KM instance knows to use its
+ // factory provisioned key instead. Alternatively, it's not an asymmetric key
+ // and therefore will not be attested.
+ Ok((None, None))
+ } else {
+ match self.get_rem_prov_attest_key(&key, caller_uid, db).context(concat!(
+ "In get_remote_provisioning_key_and_certs: Failed to get ",
+ "attestation key"
+ ))? {
+ Some(cert_chain) => Ok((
+ Some(AttestationKey {
+ keyBlob: cert_chain.private_key.to_vec(),
+ attestKeyParams: vec![],
+ issuerSubjectName: parse_subject_from_certificate(&cert_chain.batch_cert)
+ .context(concat!(
+ "In get_remote_provisioning_key_and_certs: Failed to ",
+ "parse subject."
+ ))?,
+ }),
+ Some(Certificate { encodedCertificate: cert_chain.cert_chain }),
+ )),
+ None => Ok((None, None)),
+ }
+ }
+ }
+}
/// Implementation of the IRemoteProvisioning service.
+#[derive(Default)]
pub struct RemoteProvisioningService {
- // TODO(b/179222809): Add the remote provisioner hal aidl interface when available
+ device_by_sec_level: HashMap<SecurityLevel, Asp>,
}
impl RemoteProvisioningService {
+ fn get_dev_by_sec_level(
+ &self,
+ sec_level: &SecurityLevel,
+ ) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
+ if let Some(dev) = self.device_by_sec_level.get(sec_level) {
+ dev.get_interface().context("In get_dev_by_sec_level.")
+ } else {
+ Err(error::Error::sys()).context(concat!(
+ "In get_dev_by_sec_level: Remote instance for requested security level",
+ " not found."
+ ))
+ }
+ }
+
/// Creates a new instance of the remote provisioning service
pub fn new_native_binder() -> Result<Strong<dyn IRemoteProvisioning>> {
- let result = BnRemoteProvisioning::new_binder(Self {});
- Ok(result)
+ let mut result: Self = Default::default();
+ let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?;
+ result.device_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, dev);
+ if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) {
+ result.device_by_sec_level.insert(SecurityLevel::STRONGBOX, dev);
+ }
+ Ok(BnRemoteProvisioning::new_binder(result))
}
/// Populates the AttestationPoolStatus parcelable with information about how many
@@ -54,6 +244,11 @@
let (_, _, uuid) = get_keymint_device(&sec_level)?;
DB.with::<_, Result<AttestationPoolStatus>>(|db| {
let mut db = db.borrow_mut();
+ // delete_expired_attestation_keys is always safe to call, and will remove anything
+ // older than the date at the time of calling. No work should be done on the
+ // attestation keys unless the pool status is checked first, so this call should be
+ // enough to routinely clean out expired keys.
+ db.delete_expired_attestation_keys()?;
Ok(db.get_attestation_pool_status(expired_by, &uuid)?)
})
}
@@ -68,15 +263,48 @@
/// baked in root of trust in the underlying IRemotelyProvisionedComponent instance.
pub fn generate_csr(
&self,
- _test_mode: bool,
- _num_csr: i32,
- _eek: &[u8],
- _challenge: &[u8],
- _sec_level: SecurityLevel,
+ test_mode: bool,
+ num_csr: i32,
+ eek: &[u8],
+ challenge: &[u8],
+ sec_level: SecurityLevel,
+ protected_data: &mut ProtectedData,
) -> Result<Vec<u8>> {
- // TODO(b/179222809): implement with actual remote provisioner AIDL when available. For now
- // it isnice to have some junk values
- Ok(vec![0, 1, 3, 3])
+ let dev = self.get_dev_by_sec_level(&sec_level)?;
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ let keys_to_sign = DB.with::<_, Result<Vec<MacedPublicKey>>>(|db| {
+ let mut db = db.borrow_mut();
+ Ok(db
+ .fetch_unsigned_attestation_keys(num_csr, &uuid)?
+ .iter()
+ .map(|key| MacedPublicKey { macedKey: key.to_vec() })
+ .collect())
+ })?;
+ let mut mac = Vec::<u8>::with_capacity(32);
+ map_rem_prov_error(dev.generateCertificateRequest(
+ test_mode,
+ &keys_to_sign,
+ eek,
+ challenge,
+ &mut mac,
+ protected_data,
+ ))
+ .context("In generate_csr: Failed to generate csr")?;
+ let mut cose_mac_0 = Vec::<u8>::new();
+ // TODO(b/180392379): Replace this manual CBOR generation with the cbor-serde crate as well.
+ // This generates an array consisting of the mac and the public key Maps.
+ // Just generate the actual MacedPublicKeys structure when the crate is
+ // available.
+ cose_mac_0.push((0b100_00000 | (keys_to_sign.len() + 1)) as u8);
+ cose_mac_0.push(0b010_11000); //push mac
+ cose_mac_0.push(mac.len() as u8);
+ cose_mac_0.append(&mut mac);
+ for maced_public_key in keys_to_sign {
+ if maced_public_key.macedKey.len() > 83 + 8 {
+ cose_mac_0.extend_from_slice(&maced_public_key.macedKey[8..83 + 8]);
+ }
+ }
+ Ok(cose_mac_0)
}
/// Provisions a certificate chain for a key whose CSR was included in generate_csr. The
@@ -87,6 +315,7 @@
pub fn provision_cert_chain(
&self,
public_key: &[u8],
+ batch_cert: &[u8],
certs: &[u8],
expiration_date: i64,
sec_level: SecurityLevel,
@@ -96,6 +325,7 @@
let (_, _, uuid) = get_keymint_device(&sec_level)?;
Ok(db.store_signed_attestation_certificate_chain(
public_key,
+ batch_cert,
certs, /* DER encoded certificate chain */
expiration_date,
&uuid,
@@ -107,8 +337,35 @@
/// `is_test_mode` indicates whether or not the returned public key should be marked as being
/// for testing in order to differentiate them from private keys. If the call is successful,
/// the key pair is then added to the database.
- pub fn generate_key_pair(&self, _is_test_mode: bool, _sec_level: SecurityLevel) -> Result<()> {
- Ok(())
+ pub fn generate_key_pair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> Result<()> {
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ let dev = self.get_dev_by_sec_level(&sec_level)?;
+ let mut maced_key = MacedPublicKey { macedKey: Vec::new() };
+ let priv_key =
+ map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key))
+ .context("In generate_key_pair: Failed to generated ECDSA keypair.")?;
+ // TODO(b/180392379): This is a brittle hack that relies on the consistent formatting of
+ // the returned CBOR blob in order to extract the public key.
+ let data = &maced_key.macedKey;
+ if data.len() < 85 {
+ return Err(error::Error::sys()).context(concat!(
+ "In generate_key_pair: CBOR blob returned from",
+ "RemotelyProvisionedComponent is definitely malformatted or empty."
+ ));
+ }
+ let mut raw_key: Vec<u8> = vec![0; 64];
+ raw_key[0..32].clone_from_slice(&data[18..18 + 32]);
+ raw_key[32..64].clone_from_slice(&data[53..53 + 32]);
+ DB.with::<_, Result<()>>(|db| {
+ let mut db = db.borrow_mut();
+ Ok(db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid)?)
+ })
+ }
+
+ /// Checks the security level of each available IRemotelyProvisionedComponent hal and returns
+ /// all levels in an array to the caller.
+ pub fn get_security_levels(&self) -> Result<Vec<SecurityLevel>> {
+ Ok(self.device_by_sec_level.keys().cloned().collect())
}
}
@@ -132,18 +389,26 @@
eek: &[u8],
challenge: &[u8],
sec_level: SecurityLevel,
+ protected_data: &mut ProtectedData,
) -> binder::public_api::Result<Vec<u8>> {
- map_or_log_err(self.generate_csr(test_mode, num_csr, eek, challenge, sec_level), Ok)
+ map_or_log_err(
+ self.generate_csr(test_mode, num_csr, eek, challenge, sec_level, protected_data),
+ Ok,
+ )
}
fn provisionCertChain(
&self,
public_key: &[u8],
+ batch_cert: &[u8],
certs: &[u8],
expiration_date: i64,
sec_level: SecurityLevel,
) -> binder::public_api::Result<()> {
- map_or_log_err(self.provision_cert_chain(public_key, certs, expiration_date, sec_level), Ok)
+ map_or_log_err(
+ self.provision_cert_chain(public_key, batch_cert, certs, expiration_date, sec_level),
+ Ok,
+ )
}
fn generateKeyPair(
@@ -153,4 +418,8 @@
) -> binder::public_api::Result<()> {
map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok)
}
+
+ fn getSecurityLevels(&self) -> binder::public_api::Result<Vec<SecurityLevel>> {
+ map_or_log_err(self.get_security_levels(), Ok)
+ }
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 417e3c5..6560d4d 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -16,10 +16,11 @@
//! This crate implements the IKeystoreSecurityLevel interface.
-use crate::{database::Uuid, gc::Gc, globals::get_keymint_device};
+use crate::globals::get_keymint_device;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, HardwareAuthenticatorType::HardwareAuthenticatorType,
- IKeyMintDevice::IKeyMintDevice, KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat,
+ Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, IKeyMintDevice::IKeyMintDevice,
+ KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat,
KeyMintHardwareInfo::KeyMintHardwareInfo, KeyParameter::KeyParameter,
KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
};
@@ -31,22 +32,24 @@
KeyMetadata::KeyMetadata, KeyParameters::KeyParameters,
};
-use crate::globals::ENFORCEMENTS;
+use crate::database::{CertificateInfo, KeyIdGuard, KeystoreDB};
+use crate::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY};
use crate::key_parameter::KeyParameter as KsKeyParam;
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
-use crate::utils::{check_key_permission, uid_to_android_user, Asp};
-use crate::{
- database::{CertificateInfo, KeyIdGuard},
- globals::DB,
+use crate::remote_provisioning::RemProvState;
+use crate::super_key::{KeyBlob, SuperKeyManager};
+use crate::utils::{
+ check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
+ uid_to_android_user, Asp,
};
use crate::{
- database::{DateTime, KeyMetaData, KeyMetaEntry, KeyType},
- permission::KeyPerm,
-};
-use crate::{
- database::{KeyEntry, KeyEntryLoadBits, SubComponentType},
+ database::{
+ BlobMetaData, BlobMetaEntry, DateTime, KeyEntry, KeyEntryLoadBits, KeyMetaData,
+ KeyMetaEntry, KeyType, SubComponentType, Uuid,
+ },
operation::KeystoreOperation,
operation::OperationDb,
+ permission::KeyPerm,
};
use crate::{
error::{self, map_km_error, map_or_log_err, Error, ErrorCode},
@@ -54,6 +57,7 @@
};
use anyhow::{anyhow, Context, Result};
use binder::{IBinder, Strong, ThreadState};
+use keystore2_crypto::parse_subject_from_certificate;
/// Implementation of the IKeystoreSecurityLevel Interface.
pub struct KeystoreSecurityLevel {
@@ -63,6 +67,7 @@
hw_info: KeyMintHardwareInfo,
km_uuid: Uuid,
operation_db: OperationDb,
+ rem_prov_state: RemProvState,
}
// Blob of 32 zeroes used as empty masking key.
@@ -88,6 +93,7 @@
hw_info,
km_uuid,
operation_db: OperationDb::new(),
+ rem_prov_state: RemProvState::new(security_level, km_uuid),
});
result.as_binder().set_requesting_sid(true);
Ok((result, km_uuid))
@@ -98,6 +104,7 @@
key: KeyDescriptor,
creation_result: KeyCreationResult,
user_id: u32,
+ flags: Option<i32>,
) -> Result<KeyMetadata> {
let KeyCreationResult {
keyBlob: key_blob,
@@ -133,28 +140,41 @@
let creation_date = DateTime::now().context("Trying to make creation time.")?;
let key = match key.domain {
- Domain::BLOB => {
- KeyDescriptor { domain: Domain::BLOB, blob: Some(key_blob), ..Default::default() }
- }
+ Domain::BLOB => KeyDescriptor {
+ domain: Domain::BLOB,
+ blob: Some(key_blob.to_vec()),
+ ..Default::default()
+ },
_ => DB
.with::<_, Result<KeyDescriptor>>(|db| {
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(creation_date));
-
let mut db = db.borrow_mut();
- let (need_gc, key_id) = db
+
+ let (key_blob, mut blob_metadata) = SUPER_KEY
+ .handle_super_encryption_on_key_init(
+ &mut db,
+ &LEGACY_MIGRATOR,
+ &(key.domain),
+ &key_parameters,
+ flags,
+ user_id,
+ &key_blob,
+ )
+ .context("In store_new_key. Failed to handle super encryption.")?;
+
+ let mut key_metadata = KeyMetaData::new();
+ key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
+ blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
+
+ let key_id = db
.store_new_key(
&key,
&key_parameters,
- &key_blob,
+ &(&key_blob, &blob_metadata),
&cert_info,
- &metadata,
+ &key_metadata,
&self.km_uuid,
)
.context("In store_new_key.")?;
- if need_gc {
- Gc::notify_gc();
- }
Ok(KeyDescriptor {
domain: Domain::KEY_ID,
nspace: key_id.id(),
@@ -185,7 +205,7 @@
// so that we can use it by reference like the blob provided by the key descriptor.
// Otherwise, we would have to clone the blob from the key descriptor.
let scoping_blob: Vec<u8>;
- let (km_blob, key_properties, key_id_guard) = match key.domain {
+ let (km_blob, key_properties, key_id_guard, blob_metadata) = match key.domain {
Domain::BLOB => {
check_key_permission(KeyPerm::use_(), key, &None)
.context("In create_operation: checking use permission for Domain::BLOB.")?;
@@ -201,33 +221,36 @@
},
None,
None,
+ BlobMetaData::new(),
)
}
_ => {
let (key_id_guard, mut key_entry) = DB
.with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
- db.borrow_mut().load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::KM,
- caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::KM,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ )
+ })
})
.context("In create_operation: Failed to load key blob.")?;
- scoping_blob = match key_entry.take_km_blob() {
- Some(blob) => blob,
- None => {
- return Err(Error::sys()).context(concat!(
- "In create_operation: Successfully loaded key entry,",
- " but KM blob was missing."
- ))
- }
- };
+
+ let (blob, blob_metadata) =
+ key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!(
+ "In create_operation: Successfully loaded key entry, ",
+ "but KM blob was missing."
+ ))?;
+ scoping_blob = blob;
+
(
&scoping_blob,
Some((key_id_guard.id(), key_entry.into_key_parameters())),
Some(key_id_guard),
+ blob_metadata,
)
}
};
@@ -256,6 +279,12 @@
let immediate_hat = immediate_hat.unwrap_or_default();
+ let user_id = uid_to_android_user(caller_uid);
+
+ let km_blob = SUPER_KEY
+ .unwrap_key_if_required(&blob_metadata, km_blob)
+ .context("In create_operation. Failed to handle super encryption.")?;
+
let km_dev: Strong<dyn IKeyMintDevice> = self
.keymint
.get_interface()
@@ -265,7 +294,7 @@
.upgrade_keyblob_if_required_with(
&*km_dev,
key_id_guard,
- &km_blob,
+ &(&km_blob, &blob_metadata),
&operation_parameters,
|blob| loop {
match map_km_error(km_dev.begin(
@@ -333,6 +362,15 @@
))?;
}
+ // If the caller requests any device identifier attestation tag, check that they hold the
+ // correct Android permission.
+ if params.iter().any(|kp| is_device_id_attestation_tag(kp.tag)) {
+ check_device_attestation_permissions().context(concat!(
+ "In add_certificate_parameters: ",
+ "Caller does not have the permission to attest device identifiers."
+ ))?;
+ }
+
// If we are generating/importing an asymmetric key, we need to make sure
// that NOT_BEFORE and NOT_AFTER are present.
match params.iter().find(|kp| kp.tag == Tag::ALGORITHM) {
@@ -359,7 +397,7 @@
fn generate_key(
&self,
key: &KeyDescriptor,
- attestation_key: Option<&KeyDescriptor>,
+ attest_key_descriptor: Option<&KeyDescriptor>,
params: &[KeyParameter],
flags: i32,
entropy: &[u8],
@@ -382,18 +420,108 @@
// generate_key requires the rebind permission.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In generate_key.")?;
-
+ let (attest_key, cert_chain) = match (key.domain, attest_key_descriptor) {
+ (Domain::BLOB, None) => (None, None),
+ _ => DB
+ .with::<_, Result<(Option<AttestationKey>, Option<Certificate>)>>(|db| {
+ self.get_attest_key_and_cert_chain(
+ &key,
+ caller_uid,
+ attest_key_descriptor,
+ params,
+ &mut db.borrow_mut(),
+ )
+ })
+ .context("In generate_key: Trying to get an attestation key")?,
+ };
let params = Self::add_certificate_parameters(caller_uid, params, &key)
.context("In generate_key: Trying to get aaid.")?;
let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?;
map_km_error(km_dev.addRngEntropy(entropy))
.context("In generate_key: Trying to add entropy.")?;
- let creation_result = map_km_error(km_dev.generateKey(¶ms))
+ let mut creation_result = map_km_error(km_dev.generateKey(¶ms, attest_key.as_ref()))
.context("In generate_key: While generating Key")?;
-
+ // The certificate chain ultimately gets flattened into a big DER encoded byte array,
+ // so providing that blob upfront in a single certificate entry should be fine.
+ if let Some(cert) = cert_chain {
+ creation_result.certificateChain.push(cert);
+ }
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id).context("In generate_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context("In generate_key.")
+ }
+
+ fn get_attest_key_and_cert_chain(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ attest_key_descriptor: Option<&KeyDescriptor>,
+ params: &[KeyParameter],
+ db: &mut KeystoreDB,
+ ) -> Result<(Option<AttestationKey>, Option<Certificate>)> {
+ match attest_key_descriptor {
+ None => self
+ .rem_prov_state
+ .get_remote_provisioning_key_and_certs(&key, caller_uid, params, db),
+ Some(attest_key) => Ok((
+ Some(
+ self.get_attest_key(&attest_key, caller_uid)
+ .context("In generate_key: Trying to load attest key")?,
+ ),
+ None,
+ )),
+ }
+ }
+
+ fn get_attest_key(&self, key: &KeyDescriptor, caller_uid: u32) -> Result<AttestationKey> {
+ let (km_blob, cert) = self
+ .load_attest_key_blob_and_cert(&key, caller_uid)
+ .context("In get_attest_key: Failed to load blob and cert")?;
+
+ let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert)
+ .context("In get_attest_key: Failed to parse subject from certificate.")?;
+
+ Ok(AttestationKey {
+ keyBlob: km_blob.to_vec(),
+ attestKeyParams: [].to_vec(),
+ issuerSubjectName: issuer_subject,
+ })
+ }
+
+ fn load_attest_key_blob_and_cert(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ ) -> Result<(Vec<u8>, Vec<u8>)> {
+ match key.domain {
+ Domain::BLOB => Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(
+ "In load_attest_key_blob_and_cert: Domain::BLOB attestation keys not supported",
+ ),
+ _ => {
+ let (key_id_guard, mut key_entry) = DB
+ .with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ )
+ })
+ .context("In load_attest_key_blob_and_cert: Failed to load key.")?;
+
+ let (blob, _) =
+ key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!(
+ "In load_attest_key_blob_and_cert: Successfully loaded key entry,",
+ " but KM blob was missing."
+ ))?;
+ let cert = key_entry.take_cert().ok_or_else(Error::sys).context(concat!(
+ "In load_attest_key_blob_and_cert: Successfully loaded key entry,",
+ " but cert was missing."
+ ))?;
+ Ok((blob, cert))
+ }
+ }
}
fn import_key(
@@ -444,11 +572,12 @@
let km_dev: Strong<dyn IKeyMintDevice> =
self.keymint.get_interface().context("In import_key: Trying to get the KM device")?;
- let creation_result = map_km_error(km_dev.importKey(¶ms, format, key_data))
- .context("In import_key: Trying to call importKey")?;
+ let creation_result =
+ map_km_error(km_dev.importKey(¶ms, format, key_data, None /* attestKey */))
+ .context("In import_key: Trying to call importKey")?;
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id).context("In import_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.")
}
fn import_wrapped_key(
@@ -482,6 +611,8 @@
}
let caller_uid = ThreadState::get_calling_uid();
+ let user_id = uid_to_android_user(caller_uid);
+
let key = match key.domain {
Domain::APP => KeyDescriptor {
domain: key.domain,
@@ -498,29 +629,32 @@
_ => panic!("Unreachable."),
};
- // import_wrapped_key requires the rebind permission for the new key.
+ // Import_wrapped_key requires the rebind permission for the new key.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?;
- let (wrapping_key_id_guard, wrapping_key_entry) = DB
+ let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
.with(|db| {
- db.borrow_mut().load_key_entry(
- &wrapping_key,
- KeyType::Client,
- KeyEntryLoadBits::KM,
- caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &wrapping_key,
+ KeyType::Client,
+ KeyEntryLoadBits::KM,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ )
+ })
})
.context("Failed to load wrapping key.")?;
- let wrapping_key_blob = match wrapping_key_entry.km_blob() {
- Some(blob) => blob,
- None => {
- return Err(error::Error::sys()).context(concat!(
- "No km_blob after successfully loading key.",
- " This should never happen."
- ))
- }
- };
+
+ let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry
+ .take_key_blob_info()
+ .ok_or_else(error::Error::sys)
+ .context("No km_blob after successfully loading key. This should never happen.")?;
+
+ let wrapping_key_blob =
+ SUPER_KEY.unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob).context(
+ "In import_wrapped_key. Failed to handle super encryption for wrapping key.",
+ )?;
// km_dev.importWrappedKey does not return a certificate chain.
// TODO Do we assume that all wrapped keys are symmetric?
@@ -549,12 +683,12 @@
.upgrade_keyblob_if_required_with(
&*km_dev,
Some(wrapping_key_id_guard),
- wrapping_key_blob,
+ &(&wrapping_key_blob, &wrapping_blob_metadata),
&[],
|wrapping_blob| {
let creation_result = map_km_error(km_dev.importWrappedKey(
wrapped_data,
- wrapping_key_blob,
+ wrapping_blob,
masking_key,
¶ms,
pw_sid,
@@ -565,8 +699,7 @@
)
.context("In import_wrapped_key.")?;
- let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id)
+ self.store_new_key(key, creation_result, user_id, None)
.context("In import_wrapped_key: Trying to store the new key.")
}
@@ -574,23 +707,37 @@
&self,
km_dev: &dyn IKeyMintDevice,
key_id_guard: Option<KeyIdGuard>,
- blob: &[u8],
+ blob_info: &(&KeyBlob, &BlobMetaData),
params: &[KeyParameter],
f: F,
) -> Result<(T, Option<Vec<u8>>)>
where
F: Fn(&[u8]) -> Result<T, Error>,
{
- match f(blob) {
+ match f(blob_info.0) {
Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = map_km_error(km_dev.upgradeKey(blob, params))
+ let upgraded_blob = map_km_error(km_dev.upgradeKey(blob_info.0, params))
.context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
+
+ let (upgraded_blob_to_be_stored, blob_metadata) =
+ SuperKeyManager::reencrypt_on_upgrade_if_required(blob_info.0, &upgraded_blob)
+ .context(
+ "In upgrade_keyblob_if_required_with: Failed to handle super encryption.",
+ )?;
+
+ let mut blob_metadata = blob_metadata.unwrap_or_else(BlobMetaData::new);
+ if let Some(uuid) = blob_info.1.km_uuid() {
+ blob_metadata.add(BlobMetaEntry::KmUuid(*uuid));
+ }
+
key_id_guard.map_or(Ok(()), |key_id_guard| {
DB.with(|db| {
- db.borrow_mut().set_blob(
+ let mut db = db.borrow_mut();
+ db.set_blob(
&key_id_guard,
SubComponentType::KEY_BLOB,
- Some(&upgraded_blob),
+ Some(&upgraded_blob_to_be_stored),
+ Some(&blob_metadata),
)
})
.context(concat!(
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 6aa7ed5..3a4bf82 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -20,22 +20,22 @@
use std::collections::HashMap;
+use crate::error::{self, map_or_log_err, ErrorCode};
use crate::permission::{KeyPerm, KeystorePerm};
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
check_grant_permission, check_key_permission, check_keystore_permission,
key_parameters_to_authorizations, Asp,
};
-use crate::{database::Uuid, globals::DB};
+use crate::{
+ database::Uuid,
+ globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_MIGRATOR},
+};
use crate::{database::KEYSTORE_UUID, permission};
use crate::{
database::{KeyEntryLoadBits, KeyType, SubComponentType},
error::ResponseCode,
};
-use crate::{
- error::{self, map_or_log_err, ErrorCode},
- gc::Gc,
-};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
@@ -76,6 +76,15 @@
result.uuid_by_sec_level.insert(SecurityLevel::STRONGBOX, uuid);
}
+ let uuid_by_sec_level = result.uuid_by_sec_level.clone();
+ LEGACY_MIGRATOR
+ .set_init(move || {
+ (create_thread_local_db(), uuid_by_sec_level, LEGACY_BLOB_LOADER.clone())
+ })
+ .context(
+ "In KeystoreService::new_native_binder: Trying to initialize the legacy migrator.",
+ )?;
+
let result = BnKeystoreService::new_binder(result);
result.as_binder().set_requesting_sid(true);
Ok(result)
@@ -115,15 +124,18 @@
}
fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> {
+ let caller_uid = ThreadState::get_calling_uid();
let (key_id_guard, mut key_entry) = DB
.with(|db| {
- db.borrow_mut().load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::PUBLIC,
- ThreadState::get_calling_uid(),
- |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::PUBLIC,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
+ )
+ })
})
.context("In get_key_entry, while trying to load key info.")?;
@@ -164,18 +176,20 @@
public_cert: Option<&[u8]>,
certificate_chain: Option<&[u8]>,
) -> Result<()> {
+ let caller_uid = ThreadState::get_calling_uid();
DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- let entry = match db.load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- ThreadState::get_calling_uid(),
- |k, av| {
- check_key_permission(KeyPerm::update(), k, &av)
- .context("In update_subcomponent.")
- },
- ) {
+ let entry = match LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ caller_uid,
+ |k, av| {
+ check_key_permission(KeyPerm::update(), k, &av)
+ .context("In update_subcomponent.")
+ },
+ )
+ }) {
Err(e) => match e.root_cause().downcast_ref::<Error>() {
Some(Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
_ => Err(e),
@@ -184,11 +198,12 @@
}
.context("Failed to load key entry.")?;
+ let mut db = db.borrow_mut();
if let Some((key_id_guard, key_entry)) = entry {
- db.set_blob(&key_id_guard, SubComponentType::CERT, public_cert)
+ db.set_blob(&key_id_guard, SubComponentType::CERT, public_cert, None)
.context("Failed to update cert subcomponent.")?;
- db.set_blob(&key_id_guard, SubComponentType::CERT_CHAIN, certificate_chain)
+ db.set_blob(&key_id_guard, SubComponentType::CERT_CHAIN, certificate_chain, None)
.context("Failed to update cert chain subcomponent.")?;
return Ok(());
}
@@ -261,24 +276,34 @@
Ok(()) => {}
};
- DB.with(|db| {
- let mut db = db.borrow_mut();
- db.list(k.domain, k.nspace)
- })
+ let mut result = LEGACY_MIGRATOR
+ .list_uid(k.domain, k.nspace)
+ .context("In list_entries: Trying to list legacy keys.")?;
+
+ result.append(
+ &mut DB
+ .with(|db| {
+ let mut db = db.borrow_mut();
+ db.list(k.domain, k.nspace)
+ })
+ .context("In list_entries: Trying to list keystore database.")?,
+ );
+
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
}
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
- let need_gc = DB
- .with(|db| {
+ DB.with(|db| {
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| {
check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.")
})
})
- .context("In delete_key: Trying to unbind the key.")?;
- if need_gc {
- Gc::notify_gc();
- }
+ })
+ .context("In delete_key: Trying to unbind the key.")?;
Ok(())
}
@@ -288,14 +313,17 @@
grantee_uid: i32,
access_vector: permission::KeyPermSet,
) -> Result<KeyDescriptor> {
+ let caller_uid = ThreadState::get_calling_uid();
DB.with(|db| {
- db.borrow_mut().grant(
- &key,
- ThreadState::get_calling_uid(),
- grantee_uid as u32,
- access_vector,
- |k, av| check_grant_permission(*av, k).context("During grant."),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().grant(
+ &key,
+ caller_uid,
+ grantee_uid as u32,
+ access_vector,
+ |k, av| check_grant_permission(*av, k).context("During grant."),
+ )
+ })
})
.context("In KeystoreService::grant.")
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 9872513..5ee685a 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -15,15 +15,18 @@
#![allow(dead_code)]
use crate::{
- database::EncryptedBy, database::KeyMetaData, database::KeyMetaEntry, database::KeystoreDB,
- error::Error, error::ResponseCode, legacy_blob::LegacyBlobLoader,
+ database::BlobMetaData, database::BlobMetaEntry, database::EncryptedBy, database::KeyEntry,
+ database::KeyType, database::KeystoreDB, enforcements::Enforcements, error::Error,
+ error::ResponseCode, key_parameter::KeyParameter, legacy_blob::LegacyBlobLoader,
+ legacy_migrator::LegacyMigrator,
};
use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
use anyhow::{Context, Result};
use keystore2_crypto::{
- aes_gcm_decrypt, aes_gcm_encrypt, derive_key_from_password, generate_salt, ZVec,
- AES_256_KEY_LENGTH,
+ aes_gcm_decrypt, aes_gcm_encrypt, derive_key_from_password, generate_aes256_key, generate_salt,
+ ZVec, AES_256_KEY_LENGTH,
};
+use std::ops::Deref;
use std::{
collections::HashMap,
sync::Arc,
@@ -39,13 +42,30 @@
/// secret, that is itself derived from the user's lock screen knowledge factor (LSKF).
/// When the user unlocks the device for the first time, this key is unlocked, i.e., decrypted,
/// and stays memory resident until the device reboots.
- per_boot: Option<Arc<ZVec>>,
+ per_boot: Option<SuperKey>,
/// The screen lock key works like the per boot key with the distinction that it is cleared
/// from memory when the screen lock is engaged.
/// TODO the life cycle is not fully implemented at this time.
screen_lock: Option<Arc<ZVec>>,
}
+#[derive(Default, Clone)]
+pub struct SuperKey {
+ key: Arc<ZVec>,
+ // id of the super key in the database.
+ id: i64,
+}
+
+impl SuperKey {
+ pub fn get_key(&self) -> &Arc<ZVec> {
+ &self.key
+ }
+
+ pub fn get_id(&self) -> i64 {
+ self.id
+ }
+}
+
#[derive(Default)]
struct SkmState {
user_keys: HashMap<UserId, UserSuperKeys>,
@@ -87,18 +107,17 @@
data.key_index.clear();
}
- fn install_per_boot_key_for_user(&self, user: UserId, key_id: i64, key: ZVec) {
+ fn install_per_boot_key_for_user(&self, user: UserId, super_key: SuperKey) {
let mut data = self.data.lock().unwrap();
- let key = Arc::new(key);
- data.key_index.insert(key_id, Arc::downgrade(&key));
- data.user_keys.entry(user).or_default().per_boot = Some(key);
+ data.key_index.insert(super_key.id, Arc::downgrade(&(super_key.key)));
+ data.user_keys.entry(user).or_default().per_boot = Some(super_key);
}
fn get_key(&self, key_id: &i64) -> Option<Arc<ZVec>> {
self.data.lock().unwrap().key_index.get(key_id).and_then(|k| k.upgrade())
}
- pub fn get_per_boot_key_by_user_id(&self, user_id: u32) -> Option<Arc<ZVec>> {
+ pub fn get_per_boot_key_by_user_id(&self, user_id: u32) -> Option<SuperKey> {
let data = self.data.lock().unwrap();
data.user_keys.get(&user_id).map(|e| e.per_boot.clone()).flatten()
}
@@ -109,16 +128,16 @@
/// a key derived from the given password and stored in the database.
pub fn unlock_user_key(
&self,
+ db: &mut KeystoreDB,
user: UserId,
pw: &[u8],
- db: &mut KeystoreDB,
legacy_blob_loader: &LegacyBlobLoader,
) -> Result<()> {
let (_, entry) = db
.get_or_create_key_with(
Domain::APP,
user as u64 as i64,
- &"USER_SUPER_KEY",
+ KeystoreDB::USER_SUPER_KEY_ALIAS,
crate::database::KEYSTORE_UUID,
|| {
// For backward compatibility we need to check if there is a super key present.
@@ -128,63 +147,22 @@
let super_key = match super_key {
None => {
// No legacy file was found. So we generate a new key.
- keystore2_crypto::generate_aes256_key()
+ generate_aes256_key()
.context("In create_new_key: Failed to generate AES 256 key.")?
}
Some(key) => key,
};
- // Regardless of whether we loaded an old AES128 key or a new AES256 key,
- // we derive a AES256 key and re-encrypt the key before we insert it in the
- // database. The length of the key is preserved by the encryption so we don't
- // need any extra flags to inform us which algorithm to use it with.
- let salt =
- generate_salt().context("In create_new_key: Failed to generate salt.")?;
- let derived_key = derive_key_from_password(pw, Some(&salt), AES_256_KEY_LENGTH)
- .context("In create_new_key: Failed to derive password.")?;
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::EncryptedBy(EncryptedBy::Password));
- metadata.add(KeyMetaEntry::Salt(salt));
- let (encrypted_key, iv, tag) = aes_gcm_encrypt(&super_key, &derived_key)
- .context("In create_new_key: Failed to encrypt new super key.")?;
- metadata.add(KeyMetaEntry::Iv(iv));
- metadata.add(KeyMetaEntry::AeadTag(tag));
- Ok((encrypted_key, metadata))
+ // Regardless of whether we loaded an old AES128 key or generated a new AES256
+ // key as the super key, we derive a AES256 key from the password and re-encrypt
+ // the super key before we insert it in the database. The length of the key is
+ // preserved by the encryption so we don't need any extra flags to inform us
+ // which algorithm to use it with.
+ Self::encrypt_with_password(&super_key, pw).context("In create_new_key.")
},
)
.context("In unlock_user_key: Failed to get key id.")?;
- let metadata = entry.metadata();
- let super_key = match (
- metadata.encrypted_by(),
- metadata.salt(),
- metadata.iv(),
- metadata.aead_tag(),
- entry.km_blob(),
- ) {
- (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag), Some(blob)) => {
- let key = derive_key_from_password(pw, Some(salt), AES_256_KEY_LENGTH)
- .context("In unlock_user_key: Failed to generate key from password.")?;
-
- aes_gcm_decrypt(blob, iv, tag, &key)
- .context("In unlock_user_key: Failed to decrypt key blob.")?
- }
- (enc_by, salt, iv, tag, blob) => {
- return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
- concat!(
- "In unlock_user_key: Super key has incomplete metadata.",
- "Present: encrypted_by: {}, salt: {}, iv: {}, aead_tag: {}, blob: {}."
- ),
- enc_by.is_some(),
- salt.is_some(),
- iv.is_some(),
- tag.is_some(),
- blob.is_some()
- ));
- }
- };
-
- self.install_per_boot_key_for_user(user, entry.id(), super_key);
-
+ self.populate_cache_from_super_key_blob(user, entry, pw).context("In unlock_user_key.")?;
Ok(())
}
@@ -192,12 +170,13 @@
/// The function queries `metadata.encrypted_by()` to determine the encryption key.
/// It then check if the required key is memory resident, and if so decrypts the
/// blob.
- pub fn unwrap_key(&self, blob: &[u8], metadata: &KeyMetaData) -> Result<ZVec> {
+ pub fn unwrap_key<'a>(&self, blob: &'a [u8], metadata: &BlobMetaData) -> Result<KeyBlob<'a>> {
match metadata.encrypted_by() {
Some(EncryptedBy::KeyId(key_id)) => match self.get_key(key_id) {
- Some(key) => {
- Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")
- }
+ Some(key) => Ok(KeyBlob::Sensitive(
+ Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")?,
+ SuperKey { key: key.clone(), id: *key_id },
+ )),
None => Err(Error::Rc(ResponseCode::LOCKED))
.context("In unwrap_key: Key is not usable until the user entered their LSKF."),
},
@@ -207,7 +186,7 @@
}
/// Unwraps an encrypted key blob given an encryption key.
- fn unwrap_key_with_key(blob: &[u8], metadata: &KeyMetaData, key: &[u8]) -> Result<ZVec> {
+ fn unwrap_key_with_key(blob: &[u8], metadata: &BlobMetaData, key: &[u8]) -> Result<ZVec> {
match (metadata.iv(), metadata.aead_tag()) {
(Some(iv), Some(tag)) => aes_gcm_decrypt(blob, iv, tag, key)
.context("In unwrap_key_with_key: Failed to decrypt the key blob."),
@@ -221,4 +200,415 @@
)),
}
}
+
+ /// Checks if user has setup LSKF, even when super key cache is empty for the user.
+ pub fn super_key_exists_in_db_for_user(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ ) -> Result<bool> {
+ let key_in_db = db
+ .key_exists(
+ Domain::APP,
+ user_id as u64 as i64,
+ KeystoreDB::USER_SUPER_KEY_ALIAS,
+ KeyType::Super,
+ )
+ .context("In super_key_exists_in_db_for_user.")?;
+
+ if key_in_db {
+ Ok(key_in_db)
+ } else {
+ legacy_migrator
+ .has_super_key(user_id)
+ .context("In super_key_exists_in_db_for_user: Trying to query legacy db.")
+ }
+ }
+
+ /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the
+ /// legacy database). If not, return Uninitialized state.
+ /// Otherwise, decrypt the super key from the password and return LskfUnlocked state.
+ pub fn check_and_unlock_super_key(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ pw: &[u8],
+ ) -> Result<UserState> {
+ let result = legacy_migrator
+ .with_try_migrate_super_key(user_id, pw, || db.load_super_key(user_id))
+ .context("In check_and_unlock_super_key. Failed to load super key")?;
+
+ match result {
+ Some((_, entry)) => {
+ let super_key = self
+ .populate_cache_from_super_key_blob(user_id, entry, pw)
+ .context("In check_and_unlock_super_key.")?;
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ None => Ok(UserState::Uninitialized),
+ }
+ }
+
+ /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the
+ /// legacy database). If so, return LskfLocked state.
+ /// If the password is provided, generate a new super key, encrypt with the password,
+ /// store in the database and populate the super key cache for the new user
+ /// and return LskfUnlocked state.
+ /// If the password is not provided, return Uninitialized state.
+ pub fn check_and_initialize_super_key(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ pw: Option<&[u8]>,
+ ) -> Result<UserState> {
+ let super_key_exists_in_db =
+ Self::super_key_exists_in_db_for_user(db, legacy_migrator, user_id).context(
+ "In check_and_initialize_super_key. Failed to check if super key exists.",
+ )?;
+ if super_key_exists_in_db {
+ Ok(UserState::LskfLocked)
+ } else if let Some(pw) = pw {
+ //generate a new super key.
+ let super_key = generate_aes256_key()
+ .context("In check_and_initialize_super_key: Failed to generate AES 256 key.")?;
+ //derive an AES256 key from the password and re-encrypt the super key
+ //before we insert it in the database.
+ let (encrypted_super_key, blob_metadata) = Self::encrypt_with_password(&super_key, pw)
+ .context("In check_and_initialize_super_key.")?;
+
+ let key_entry = db
+ .store_super_key(user_id, &(&encrypted_super_key, &blob_metadata))
+ .context("In check_and_initialize_super_key. Failed to store super key.")?;
+
+ let super_key = self
+ .populate_cache_from_super_key_blob(user_id, key_entry, pw)
+ .context("In check_and_initialize_super_key.")?;
+ Ok(UserState::LskfUnlocked(super_key))
+ } else {
+ Ok(UserState::Uninitialized)
+ }
+ }
+
+ //helper function to populate super key cache from the super key blob loaded from the database
+ fn populate_cache_from_super_key_blob(
+ &self,
+ user_id: u32,
+ entry: KeyEntry,
+ pw: &[u8],
+ ) -> Result<SuperKey> {
+ let super_key = Self::extract_super_key_from_key_entry(entry, pw).context(
+ "In populate_cache_from_super_key_blob. Failed to extract super key from key entry",
+ )?;
+ self.install_per_boot_key_for_user(user_id, super_key.clone());
+ Ok(super_key)
+ }
+
+ /// Extracts super key from the entry loaded from the database
+ pub fn extract_super_key_from_key_entry(entry: KeyEntry, pw: &[u8]) -> Result<SuperKey> {
+ if let Some((blob, metadata)) = entry.key_blob_info() {
+ let key = match (
+ metadata.encrypted_by(),
+ metadata.salt(),
+ metadata.iv(),
+ metadata.aead_tag(),
+ ) {
+ (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => {
+ let key = derive_key_from_password(pw, Some(salt), AES_256_KEY_LENGTH).context(
+ "In extract_super_key_from_key_entry: Failed to generate key from password.",
+ )?;
+
+ aes_gcm_decrypt(blob, iv, tag, &key).context(
+ "In extract_super_key_from_key_entry: Failed to decrypt key blob.",
+ )?
+ }
+ (enc_by, salt, iv, tag) => {
+ return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
+ concat!(
+ "In extract_super_key_from_key_entry: Super key has incomplete metadata.",
+ "Present: encrypted_by: {}, salt: {}, iv: {}, aead_tag: {}."
+ ),
+ enc_by.is_some(),
+ salt.is_some(),
+ iv.is_some(),
+ tag.is_some()
+ ));
+ }
+ };
+ Ok(SuperKey { key: Arc::new(key), id: entry.id() })
+ } else {
+ Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context("In extract_super_key_from_key_entry: No key blob info.")
+ }
+ }
+
+ /// Encrypts the super key from a key derived from the password, before storing in the database.
+ pub fn encrypt_with_password(super_key: &[u8], pw: &[u8]) -> Result<(Vec<u8>, BlobMetaData)> {
+ let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?;
+ let derived_key = derive_key_from_password(pw, Some(&salt), AES_256_KEY_LENGTH)
+ .context("In encrypt_with_password: Failed to derive password.")?;
+ let mut metadata = BlobMetaData::new();
+ metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ metadata.add(BlobMetaEntry::Salt(salt));
+ let (encrypted_key, iv, tag) = aes_gcm_encrypt(super_key, &derived_key)
+ .context("In encrypt_with_password: Failed to encrypt new super key.")?;
+ metadata.add(BlobMetaEntry::Iv(iv));
+ metadata.add(BlobMetaEntry::AeadTag(tag));
+ Ok((encrypted_key, metadata))
+ }
+
+ // Encrypt the given key blob with the user's super key, if the super key exists and the device
+ // is unlocked. If the super key exists and the device is locked, or LSKF is not setup,
+ // return error. Note that it is out of the scope of this function to check if super encryption
+ // is required. Such check should be performed before calling this function.
+ fn super_encrypt_on_key_init(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ key_blob: &[u8],
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ match UserState::get(db, legacy_migrator, self, user_id)
+ .context("In super_encrypt. Failed to get user state.")?
+ {
+ UserState::LskfUnlocked(super_key) => {
+ Self::encrypt_with_super_key(key_blob, &super_key)
+ .context("In super_encrypt_on_key_init. Failed to encrypt the key.")
+ }
+ UserState::LskfLocked => {
+ Err(Error::Rc(ResponseCode::LOCKED)).context("In super_encrypt. Device is locked.")
+ }
+ UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED))
+ .context("In super_encrypt. LSKF is not setup for the user."),
+ }
+ }
+
+ //Helper function to encrypt a key with the given super key. Callers should select which super
+ //key to be used. This is called when a key is super encrypted at its creation as well as at its
+ //upgrade.
+ fn encrypt_with_super_key(
+ key_blob: &[u8],
+ super_key: &SuperKey,
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ let mut metadata = BlobMetaData::new();
+ let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key))
+ .context("In encrypt_with_super_key: Failed to encrypt new super key.")?;
+ metadata.add(BlobMetaEntry::Iv(iv));
+ metadata.add(BlobMetaEntry::AeadTag(tag));
+ metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key.id)));
+ Ok((encrypted_key, metadata))
+ }
+
+ /// Check if super encryption is required and if so, super-encrypt the key to be stored in
+ /// the database.
+ #[allow(clippy::clippy::too_many_arguments)]
+ pub fn handle_super_encryption_on_key_init(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ domain: &Domain,
+ key_parameters: &[KeyParameter],
+ flags: Option<i32>,
+ user_id: u32,
+ key_blob: &[u8],
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ match (*domain, Enforcements::super_encryption_required(key_parameters, flags)) {
+ (Domain::APP, true) => {
+ self.super_encrypt_on_key_init(db, legacy_migrator, user_id, &key_blob).context(
+ "In handle_super_encryption_on_key_init.
+ Failed to super encrypt the key.",
+ )
+ }
+ _ => Ok((key_blob.to_vec(), BlobMetaData::new())),
+ }
+ }
+
+ /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using
+ /// the relevant super key.
+ pub fn unwrap_key_if_required<'a>(
+ &self,
+ metadata: &BlobMetaData,
+ key_blob: &'a [u8],
+ ) -> Result<KeyBlob<'a>> {
+ if Self::key_super_encrypted(&metadata) {
+ let unwrapped_key = self
+ .unwrap_key(key_blob, metadata)
+ .context("In unwrap_key_if_required. Error in unwrapping the key.")?;
+ Ok(unwrapped_key)
+ } else {
+ Ok(KeyBlob::Ref(key_blob))
+ }
+ }
+
+ /// Check if a given key needs re-super-encryption, from its KeyBlob type.
+ /// If so, re-super-encrypt the key and return a new set of metadata,
+ /// containing the new super encryption information.
+ pub fn reencrypt_on_upgrade_if_required<'a>(
+ key_blob_before_upgrade: &KeyBlob,
+ key_after_upgrade: &'a [u8],
+ ) -> Result<(KeyBlob<'a>, Option<BlobMetaData>)> {
+ match key_blob_before_upgrade {
+ KeyBlob::Sensitive(_, super_key) => {
+ let (key, metadata) = Self::encrypt_with_super_key(key_after_upgrade, super_key)
+ .context(concat!(
+ "In reencrypt_on_upgrade_if_required. ",
+ "Failed to re-super-encrypt key on key upgrade."
+ ))?;
+ Ok((KeyBlob::NonSensitive(key), Some(metadata)))
+ }
+ _ => Ok((KeyBlob::Ref(key_after_upgrade), None)),
+ }
+ }
+
+ // Helper function to decide if a key is super encrypted, given metadata.
+ fn key_super_encrypted(metadata: &BlobMetaData) -> bool {
+ if let Some(&EncryptedBy::KeyId(_)) = metadata.encrypted_by() {
+ return true;
+ }
+ false
+ }
+}
+
+/// This enum represents different states of the user's life cycle in the device.
+/// For now, only three states are defined. More states may be added later.
+pub enum UserState {
+ // The user has registered LSKF and has unlocked the device by entering PIN/Password,
+ // and hence the per-boot super key is available in the cache.
+ LskfUnlocked(SuperKey),
+ // The user has registered LSKF, but has not unlocked the device using password, after reboot.
+ // Hence the per-boot super-key(s) is not available in the cache.
+ // However, the encrypted super key is available in the database.
+ LskfLocked,
+ // There's no user in the device for the given user id, or the user with the user id has not
+ // setup LSKF.
+ Uninitialized,
+}
+
+impl UserState {
+ pub fn get(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => Ok(UserState::LskfUnlocked(super_key)),
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If so, return locked user state.
+ if SuperKeyManager::super_key_exists_in_db_for_user(db, legacy_migrator, user_id)
+ .context("In get.")?
+ {
+ Ok(UserState::LskfLocked)
+ } else {
+ Ok(UserState::Uninitialized)
+ }
+ }
+ }
+ }
+
+ /// Queries user state when serving password change requests.
+ pub fn get_with_password_changed(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ password: Option<&[u8]>,
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => {
+ if password.is_none() {
+ //transitioning to swiping, delete only the super key in database and cache, and
+ //super-encrypted keys in database (and in KM)
+ Self::reset_user(db, skm, legacy_migrator, user_id, true).context(
+ "In get_with_password_changed: Trying to delete keys from the db.",
+ )?;
+ //Lskf is now removed in Keystore
+ Ok(UserState::Uninitialized)
+ } else {
+ //Keystore won't be notified when changing to a new password when LSKF is
+ //already setup. Therefore, ideally this path wouldn't be reached.
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ }
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If so, return LskfLocked state.
+ //Otherwise, i) if the password is provided, initialize the super key and return
+ //LskfUnlocked state ii) if password is not provided, return Uninitialized state.
+ skm.check_and_initialize_super_key(db, legacy_migrator, user_id, password)
+ }
+ }
+ }
+
+ /// Queries user state when serving password unlock requests.
+ pub fn get_with_password_unlock(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ password: &[u8],
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => {
+ log::info!("In get_with_password_unlock. Trying to unlock when already unlocked.");
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If not, return Uninitialized state.
+ //Otherwise, try to unlock the super key and if successful,
+ //return LskfUnlocked state
+ skm.check_and_unlock_super_key(db, legacy_migrator, user_id, password)
+ .context("In get_with_password_unlock. Failed to unlock super key.")
+ }
+ }
+ }
+
+ /// Delete all the keys created on behalf of the user.
+ /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super
+ /// encrypted keys.
+ pub fn reset_user(
+ db: &mut KeystoreDB,
+ skm: &SuperKeyManager,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ // mark keys created on behalf of the user as unreferenced.
+ legacy_migrator
+ .bulk_delete_user(user_id, keep_non_super_encrypted_keys)
+ .context("In reset_user: Trying to delete legacy keys.")?;
+ db.unbind_keys_for_user(user_id as u32, keep_non_super_encrypted_keys)
+ .context("In reset user. Error in unbinding keys.")?;
+
+ //delete super key in cache, if exists
+ skm.forget_all_keys_for_user(user_id as u32);
+ Ok(())
+ }
+}
+
+/// This enum represents three states a KeyMint Blob can be in, w.r.t super encryption.
+/// `Sensitive` holds the non encrypted key and a reference to its super key.
+/// `NonSensitive` holds a non encrypted key that is never supposed to be encrypted.
+/// `Ref` holds a reference to a key blob when it does not need to be modified if its
+/// life time allows it.
+pub enum KeyBlob<'a> {
+ Sensitive(ZVec, SuperKey),
+ NonSensitive(Vec<u8>),
+ Ref(&'a [u8]),
+}
+
+/// Deref returns a reference to the key material in any variant.
+impl<'a> Deref for KeyBlob<'a> {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Sensitive(key, _) => &key,
+ Self::NonSensitive(key) => &key,
+ Self::Ref(key) => key,
+ }
+ }
}
diff --git a/keystore2/src/user_manager.rs b/keystore2/src/user_manager.rs
new file mode 100644
index 0000000..3c393c5
--- /dev/null
+++ b/keystore2/src/user_manager.rs
@@ -0,0 +1,120 @@
+// Copyright 2021, 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.
+
+//! This module implements IKeystoreUserManager AIDL interface.
+
+use crate::error::map_or_log_err;
+use crate::error::Error as KeystoreError;
+use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
+use crate::permission::KeystorePerm;
+use crate::super_key::UserState;
+use crate::utils::check_keystore_permission;
+use android_security_usermanager::aidl::android::security::usermanager::IKeystoreUserManager::{
+ BnKeystoreUserManager, IKeystoreUserManager,
+};
+use android_security_usermanager::binder::{Interface, Result as BinderResult};
+use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
+use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use anyhow::{Context, Result};
+use binder::{IBinder, Strong};
+
+/// This struct is defined to implement the aforementioned AIDL interface.
+/// As of now, it is an empty struct.
+pub struct UserManager;
+
+impl UserManager {
+ /// Create a new instance of Keystore User Manager service.
+ pub fn new_native_binder() -> Result<Strong<dyn IKeystoreUserManager>> {
+ let result = BnKeystoreUserManager::new_binder(Self);
+ result.as_binder().set_requesting_sid(true);
+ Ok(result)
+ }
+
+ fn on_user_password_changed(user_id: i32, password: Option<&[u8]>) -> Result<()> {
+ //Check permission. Function should return if this failed. Therefore having '?' at the end
+ //is very important.
+ check_keystore_permission(KeystorePerm::change_password())
+ .context("In on_user_password_changed.")?;
+
+ match DB
+ .with(|db| {
+ UserState::get_with_password_changed(
+ &mut db.borrow_mut(),
+ &LEGACY_MIGRATOR,
+ &SUPER_KEY,
+ user_id as u32,
+ password,
+ )
+ })
+ .context("In on_user_password_changed.")?
+ {
+ UserState::LskfLocked => {
+ // Error - password can not be changed when the device is locked
+ Err(KeystoreError::Rc(ResponseCode::LOCKED))
+ .context("In on_user_password_changed. Device is locked.")
+ }
+ _ => {
+ // LskfLocked is the only error case for password change
+ Ok(())
+ }
+ }
+ }
+
+ fn add_or_remove_user(user_id: i32) -> Result<()> {
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::change_user()).context("In add_or_remove_user.")?;
+ DB.with(|db| {
+ UserState::reset_user(
+ &mut db.borrow_mut(),
+ &SUPER_KEY,
+ &LEGACY_MIGRATOR,
+ user_id as u32,
+ false,
+ )
+ })
+ .context("In add_or_remove_user: Trying to delete keys from db.")
+ }
+
+ fn clear_namespace(domain: Domain, nspace: i64) -> Result<()> {
+ // Permission check. Must return on error. Do not touch the '?'.
+ check_keystore_permission(KeystorePerm::clear_uid()).context("In clear_namespace.")?;
+
+ LEGACY_MIGRATOR
+ .bulk_delete_uid(domain, nspace)
+ .context("In clear_namespace: Trying to delete legacy keys.")?;
+ DB.with(|db| db.borrow_mut().unbind_keys_for_namespace(domain, nspace))
+ .context("In clear_namespace: Trying to delete keys from db.")
+ }
+}
+
+impl Interface for UserManager {}
+
+impl IKeystoreUserManager for UserManager {
+ fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> {
+ map_or_log_err(Self::on_user_password_changed(user_id, password), Ok)
+ }
+
+ fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
+ map_or_log_err(Self::add_or_remove_user(user_id), Ok)
+ }
+
+ fn onUserRemoved(&self, user_id: i32) -> BinderResult<()> {
+ map_or_log_err(Self::add_or_remove_user(user_id), Ok)
+ }
+
+ fn clearNamespace(&self, domain: Domain, nspace: i64) -> BinderResult<()> {
+ map_or_log_err(Self::clear_namespace(domain, nspace), Ok)
+ }
+}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 8e161b7..2748025 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -15,12 +15,13 @@
//! This module implements utility functions used by the Keystore 2.0 service
//! implementation.
-use crate::error::Error;
+use crate::error::{map_binder_status, Error, ErrorCode};
use crate::permission;
use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- KeyCharacteristics::KeyCharacteristics,
+ KeyCharacteristics::KeyCharacteristics, Tag::Tag,
};
+use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_security_apc::aidl::android::security::apc::{
IProtectedConfirmation::{FLAG_UI_OPTION_INVERTED, FLAG_UI_OPTION_MAGNIFIED},
ResponseCode::ResponseCode as ApcResponseCode,
@@ -88,6 +89,34 @@
})
}
+/// This function checks whether a given tag corresponds to the access of device identifiers.
+pub fn is_device_id_attestation_tag(tag: Tag) -> bool {
+ matches!(tag, Tag::ATTESTATION_ID_IMEI | Tag::ATTESTATION_ID_MEID | Tag::ATTESTATION_ID_SERIAL)
+}
+
+/// This function checks whether the calling app has the Android permissions needed to attest device
+/// identifiers. It throws an error if the permissions cannot be verified, or if the caller doesn't
+/// have the right permissions, and returns silently otherwise.
+pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
+ let permission_controller: binder::Strong<dyn IPermissionController::IPermissionController> =
+ binder::get_interface("permission")?;
+
+ let binder_result = permission_controller.checkPermission(
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ ThreadState::get_calling_pid(),
+ ThreadState::get_calling_uid() as i32,
+ );
+ let has_permissions = map_binder_status(binder_result)
+ .context("In check_device_attestation_permissions: checkPermission failed")?;
+ match has_permissions {
+ true => Ok(()),
+ false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)).context(concat!(
+ "In check_device_attestation_permissions: ",
+ "caller does not have the permission to attest device IDs"
+ )),
+ }
+}
+
/// Thread safe wrapper around SpIBinder. It is safe to have SpIBinder smart pointers to the
/// same object in multiple threads, but cloning a SpIBinder is not thread safe.
/// Keystore frequently hands out binder tokens to the security level interface. If this
@@ -193,3 +222,21 @@
pub fn uid_to_android_user(uid: u32) -> u32 {
uid / AID_USER_OFFSET
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::Result;
+
+ #[test]
+ fn check_device_attestation_permissions_test() -> Result<()> {
+ check_device_attestation_permissions().or_else(|error| {
+ match error.root_cause().downcast_ref::<Error>() {
+ // Expected: the context for this test might not be allowed to attest device IDs.
+ Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
+ // Other errors are unexpected
+ _ => Err(error),
+ }
+ })
+ }
+}
diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp
new file mode 100644
index 0000000..77ec57d
--- /dev/null
+++ b/keystore2/src/vintf/Android.bp
@@ -0,0 +1,79 @@
+// Copyright 2021, 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_library {
+ name: "libkeystore2_vintf_rust",
+ crate_name: "keystore2_vintf",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "libkeystore2_vintf_bindgen",
+ ],
+ shared_libs: [
+ "libkeystore2_vintf_cpp",
+ "libvintf",
+ ],
+}
+
+cc_library {
+ name: "libkeystore2_vintf_cpp",
+ srcs: [
+ "vintf.cpp",
+ ],
+ shared_libs: [
+ "libvintf",
+ ],
+}
+
+rust_bindgen {
+ name: "libkeystore2_vintf_bindgen",
+ wrapper_src: "vintf.hpp",
+ crate_name: "keystore2_vintf_bindgen",
+ source_stem: "bindings",
+ host_supported: true,
+ shared_libs: ["libvintf"],
+ bindgen_flags: [
+ "--size_t-is-usize",
+ "--whitelist-function", "getHalNames",
+ "--whitelist-function", "getHalNamesAndVersions",
+ "--whitelist-function", "getAidlInstances",
+ "--whitelist-function", "freeNames",
+ ],
+}
+
+rust_test {
+ name: "keystore2_vintf_test",
+ crate_name: "keystore2_vintf_test",
+ srcs: ["lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ rustlibs: [
+ "libkeystore2_vintf_bindgen",
+ ],
+ static_libs: [
+ "libkeystore2_vintf_cpp",
+ ],
+ shared_libs: [
+ "libc++",
+ "libvintf",
+ ],
+}
diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs
new file mode 100644
index 0000000..c3d6d8a
--- /dev/null
+++ b/keystore2/src/vintf/lib.rs
@@ -0,0 +1,99 @@
+// Copyright 2021, 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.
+
+//! Bindings for getting the list of HALs.
+
+use keystore2_vintf_bindgen::{freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::str::Utf8Error;
+
+/// A struct that contains a list of HALs (optionally with version numbers).
+/// To use it, call as_vec to get a Vec view of the data it contains.
+pub struct HalNames {
+ data: *mut *mut c_char,
+ len: usize,
+}
+
+impl Drop for HalNames {
+ fn drop(&mut self) {
+ // Safety: The memory is allocated by our C shim so it must free it as well.
+ unsafe { freeNames(self.data, self.len) }
+ }
+}
+
+impl<'a> HalNames {
+ /// Get a Vec view of the list of HALs.
+ pub fn as_vec(&'a self) -> Result<Vec<&'a str>, Utf8Error> {
+ // Safety: self.data contains self.len C strings.
+ // The lifetimes ensure that the HalNames (and hence the strings) live
+ // at least as long as the returned vector.
+ unsafe { (0..self.len).map(|i| CStr::from_ptr(*self.data.add(i)).to_str()) }.collect()
+ }
+}
+
+/// Gets all HAL names.
+/// Note that this is not a zero-cost shim: it will make copies of the strings.
+pub fn get_hal_names() -> HalNames {
+ let mut len: usize = 0;
+ // Safety: We'll wrap this in HalNames to free the memory it allocates.
+ // It stores the size of the array it returns in len.
+ let raw_strs = unsafe { getHalNames(&mut len) };
+ HalNames { data: raw_strs, len }
+}
+
+/// Gets all HAL names and versions.
+/// Note that this is not a zero-cost shim: it will make copies of the strings.
+pub fn get_hal_names_and_versions() -> HalNames {
+ let mut len: usize = 0;
+ // Safety: We'll wrap this in HalNames to free the memory it allocates.
+ // It stores the size of the array it returns in len.
+ let raw_strs = unsafe { getHalNamesAndVersions(&mut len) };
+ HalNames { data: raw_strs, len }
+}
+
+/// Gets the instances of the given package, version, and interface tuple.
+/// Note that this is not a zero-cost shim: it will make copies of the strings.
+pub fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> HalNames {
+ let mut len: usize = 0;
+ let packages = CString::new(package).expect("Failed to make CString from package.");
+ let interface_name =
+ CString::new(interface_name).expect("Failed to make CString from interface_name.");
+ // Safety: We'll wrap this in HalNames to free the memory it allocates.
+ // It stores the size of the array it returns in len.
+ let raw_strs =
+ unsafe { getAidlInstances(&mut len, packages.as_ptr(), version, interface_name.as_ptr()) };
+ HalNames { data: raw_strs, len }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test() -> Result<(), Utf8Error> {
+ let result = get_hal_names();
+ let names = result.as_vec()?;
+ assert_ne!(names.len(), 0);
+
+ let result = get_hal_names_and_versions();
+ let names_and_versions = result.as_vec()?;
+ assert_ne!(names_and_versions.len(), 0);
+
+ assert!(names_and_versions.len() >= names.len());
+
+ Ok(())
+ }
+}
diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp
new file mode 100644
index 0000000..dbdc046
--- /dev/null
+++ b/keystore2/src/vintf/vintf.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "vintf.hpp"
+
+#include <vintf/HalManifest.h>
+#include <vintf/VintfObject.h>
+
+// Converts a set<string> into a C-style array of C strings.
+static char** convert(const std::set<std::string>& names) {
+ char** ret = new char*[names.size()];
+ char** ptr = ret;
+ for (const auto& name : names) {
+ *(ptr++) = strdup(name.c_str());
+ }
+ return ret;
+}
+
+char** getHalNames(size_t* len) {
+ auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getHalNames();
+ *len = names.size();
+ return convert(names);
+}
+
+char** getHalNamesAndVersions(size_t* len) {
+ auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getHalNamesAndVersions();
+ *len = names.size();
+ return convert(names);
+}
+
+char** getAidlInstances(size_t* len, const char* package, size_t version,
+ const char* interfaceName) {
+ auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getAidlInstances(package, version, interfaceName);
+ *len = names.size();
+ return convert(names);
+}
+
+void freeNames(char** names, size_t len) {
+ for (int i = 0; i < len; i++) {
+ free(names[i]);
+ }
+ delete[] names;
+}
diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp
new file mode 100644
index 0000000..75e80f6
--- /dev/null
+++ b/keystore2/src/vintf/vintf.hpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef __VINTF_H__
+#define __VINTF_H__
+
+#include <stddef.h>
+
+extern "C" {
+
+char** getHalNames(size_t* len);
+char** getHalNamesAndVersions(size_t* len);
+char** getAidlInstances(size_t* len, const char* package, size_t version,
+ const char* interfaceName);
+void freeNames(char** names, size_t len);
+}
+
+#endif // __VINTF_H__
diff --git a/keystore2/system_property/Android.bp b/keystore2/system_property/Android.bp
new file mode 100644
index 0000000..5a13c90
--- /dev/null
+++ b/keystore2/system_property/Android.bp
@@ -0,0 +1,52 @@
+// Copyright 2021, 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_bindgen {
+ name: "libkeystore2_system_property_bindgen",
+ wrapper_src: "system_property_bindgen.hpp",
+ crate_name: "keystore2_system_property_bindgen",
+ source_stem: "bindings",
+
+ bindgen_flags: [
+ "--size_t-is-usize",
+ "--whitelist-function=__system_property_find",
+ "--whitelist-function=__system_property_read_callback",
+ "--whitelist-function=__system_property_wait",
+ ],
+}
+
+rust_library {
+ name: "libkeystore2_system_property-rust",
+ crate_name: "keystore2_system_property",
+ srcs: [
+ "lib.rs",
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libkeystore2_system_property_bindgen",
+ "libthiserror",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/keystore2/system_property/lib.rs b/keystore2/system_property/lib.rs
new file mode 100644
index 0000000..f14cf0e
--- /dev/null
+++ b/keystore2/system_property/lib.rs
@@ -0,0 +1,148 @@
+// Copyright 2021, 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.
+
+//! This crate provides the PropertyWatcher type, which watches for changes
+//! in Android system properties.
+
+use std::os::raw::c_char;
+use std::ptr::null_mut;
+use std::{
+ ffi::{c_void, CStr, CString},
+ str::Utf8Error,
+};
+use thiserror::Error;
+
+/// Errors this crate can generate
+#[derive(Error, Debug)]
+pub enum PropertyWatcherError {
+ /// We can't watch for a property whose name contains a NUL character.
+ #[error("Cannot convert name to C string")]
+ BadNameError(#[from] std::ffi::NulError),
+ /// We can only watch for properties that exist when the watcher is created.
+ #[error("System property is absent")]
+ SystemPropertyAbsent,
+ /// __system_property_wait timed out despite being given no timeout.
+ #[error("Wait failed")]
+ WaitFailed,
+ /// read callback was not called
+ #[error("__system_property_read_callback did not call callback")]
+ ReadCallbackNotCalled,
+ /// read callback gave us a NULL pointer
+ #[error("__system_property_read_callback gave us a NULL pointer instead of a string")]
+ MissingCString,
+ /// read callback gave us a bad C string
+ #[error("__system_property_read_callback gave us a non-UTF8 C string")]
+ BadCString(#[from] Utf8Error),
+ /// read callback returned an error
+ #[error("Callback failed")]
+ CallbackError(#[from] anyhow::Error),
+}
+
+/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, PropertyWatcherError>;
+
+/// PropertyWatcher takes the name of an Android system property such
+/// as `keystore.boot_level`; it can report the current value of this
+/// property, or wait for it to change.
+pub struct PropertyWatcher {
+ prop_info: *const keystore2_system_property_bindgen::prop_info,
+ serial: keystore2_system_property_bindgen::__uint32_t,
+}
+
+impl PropertyWatcher {
+ /// Create a PropertyWatcher for the named system property.
+ pub fn new(name: &str) -> Result<Self> {
+ let cstr = CString::new(name)?;
+ // Unsafe FFI call. We generate the CStr in this function
+ // and so ensure it is valid during call.
+ // Returned pointer is valid for the lifetime of the program.
+ let prop_info =
+ unsafe { keystore2_system_property_bindgen::__system_property_find(cstr.as_ptr()) };
+ if prop_info.is_null() {
+ Err(PropertyWatcherError::SystemPropertyAbsent)
+ } else {
+ Ok(Self { prop_info, serial: 0 })
+ }
+ }
+
+ fn read_raw(&self, mut f: impl FnOnce(Option<&CStr>, Option<&CStr>)) {
+ // Unsafe function converts values passed to us by
+ // __system_property_read_callback to Rust form
+ // and pass them to inner callback.
+ unsafe extern "C" fn callback(
+ res_p: *mut c_void,
+ name: *const c_char,
+ value: *const c_char,
+ _: keystore2_system_property_bindgen::__uint32_t,
+ ) {
+ let name = if name.is_null() { None } else { Some(CStr::from_ptr(name)) };
+ let value = if value.is_null() { None } else { Some(CStr::from_ptr(value)) };
+ let f = &mut *res_p.cast::<&mut dyn FnMut(Option<&CStr>, Option<&CStr>)>();
+ f(name, value);
+ }
+
+ let mut f: &mut dyn FnOnce(Option<&CStr>, Option<&CStr>) = &mut f;
+
+ // Unsafe block for FFI call. We convert the FnOnce
+ // to a void pointer, and unwrap it in our callback.
+ unsafe {
+ keystore2_system_property_bindgen::__system_property_read_callback(
+ self.prop_info,
+ Some(callback),
+ &mut f as *mut _ as *mut c_void,
+ )
+ }
+ }
+
+ /// Call the passed function, passing it the name and current value
+ /// of this system property. See documentation for
+ /// `__system_property_read_callback` for details.
+ pub fn read<T, F>(&self, f: F) -> Result<T>
+ where
+ F: FnOnce(&str, &str) -> anyhow::Result<T>,
+ {
+ let mut result = Err(PropertyWatcherError::ReadCallbackNotCalled);
+ self.read_raw(|name, value| {
+ // use a wrapping closure as an erzatz try block.
+ result = (|| {
+ let name = name.ok_or(PropertyWatcherError::MissingCString)?.to_str()?;
+ let value = value.ok_or(PropertyWatcherError::MissingCString)?.to_str()?;
+ f(name, value).map_err(PropertyWatcherError::CallbackError)
+ })()
+ });
+ result
+ }
+
+ /// Wait for the system property to change. This
+ /// records the serial number of the last change, so
+ /// race conditions are avoided.
+ pub fn wait(&mut self) -> Result<()> {
+ let mut new_serial = self.serial;
+ // Unsafe block to call __system_property_wait.
+ // All arguments are private to PropertyWatcher so we
+ // can be confident they are valid.
+ if !unsafe {
+ keystore2_system_property_bindgen::__system_property_wait(
+ self.prop_info,
+ self.serial,
+ &mut new_serial,
+ null_mut(),
+ )
+ } {
+ return Err(PropertyWatcherError::WaitFailed);
+ }
+ self.serial = new_serial;
+ Ok(())
+ }
+}
diff --git a/keystore2/system_property/system_property_bindgen.hpp b/keystore2/system_property/system_property_bindgen.hpp
new file mode 100644
index 0000000..e3c1ade
--- /dev/null
+++ b/keystore2/system_property/system_property_bindgen.hpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+#pragma once
+
+#include "sys/system_properties.h"
diff --git a/keystore2/vpnprofilestore/Android.bp b/keystore2/vpnprofilestore/Android.bp
new file mode 100644
index 0000000..7ddf0d6
--- /dev/null
+++ b/keystore2/vpnprofilestore/Android.bp
@@ -0,0 +1,57 @@
+// Copyright 2021, 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_library {
+ name: "libvpnprofilestore-rust",
+ crate_name: "vpnprofilestore",
+ srcs: [
+ "lib.rs",
+ ],
+ rustlibs: [
+ "android.security.vpnprofilestore-rust",
+ "libanyhow",
+ "libbinder_rs",
+ "libkeystore2",
+ "liblog_rust",
+ "librusqlite",
+ "libthiserror",
+ ],
+}
+
+rust_test {
+ name: "vpnprofilestore_test",
+ crate_name: "vpnprofilestore",
+ srcs: ["lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ rustlibs: [
+ "android.security.vpnprofilestore-rust",
+ "libanyhow",
+ "libbinder_rs",
+ "libkeystore2",
+ "libkeystore2_test_utils",
+ "liblog_rust",
+ "librusqlite",
+ "libthiserror",
+ ],
+}
diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/vpnprofilestore/lib.rs
new file mode 100644
index 0000000..f92eacd
--- /dev/null
+++ b/keystore2/vpnprofilestore/lib.rs
@@ -0,0 +1,443 @@
+// Copyright 2020, 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.
+
+//! Implements the android.security.vpnprofilestore interface.
+
+use android_security_vpnprofilestore::aidl::android::security::vpnprofilestore::{
+ IVpnProfileStore::BnVpnProfileStore, IVpnProfileStore::IVpnProfileStore,
+ IVpnProfileStore::ERROR_PROFILE_NOT_FOUND, IVpnProfileStore::ERROR_SYSTEM_ERROR,
+};
+use android_security_vpnprofilestore::binder::{Result as BinderResult, Status as BinderStatus};
+use anyhow::{Context, Result};
+use binder::{ExceptionCode, Strong, ThreadState};
+use keystore2::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
+use rusqlite::{
+ params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
+};
+use std::{
+ collections::HashSet,
+ path::{Path, PathBuf},
+};
+
+struct DB {
+ conn: Connection,
+}
+
+impl DB {
+ fn new(db_file: &Path) -> Result<Self> {
+ let mut db = Self {
+ conn: Connection::open(db_file).context("Failed to initialize SQLite connection.")?,
+ };
+ db.init_tables().context("Trying to initialize vpnstore db.")?;
+ Ok(db)
+ }
+
+ fn with_transaction<T, F>(&mut self, behavior: TransactionBehavior, f: F) -> Result<T>
+ where
+ F: Fn(&Transaction) -> Result<T>,
+ {
+ loop {
+ match self
+ .conn
+ .transaction_with_behavior(behavior)
+ .context("In with_transaction.")
+ .and_then(|tx| f(&tx).map(|result| (result, tx)))
+ .and_then(|(result, tx)| {
+ tx.commit().context("In with_transaction: Failed to commit transaction.")?;
+ Ok(result)
+ }) {
+ Ok(result) => break Ok(result),
+ Err(e) => {
+ if Self::is_locked_error(&e) {
+ std::thread::sleep(std::time::Duration::from_micros(500));
+ continue;
+ } else {
+ return Err(e).context("In with_transaction.");
+ }
+ }
+ }
+ }
+ }
+
+ fn is_locked_error(e: &anyhow::Error) -> bool {
+ matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
+ Some(rusqlite::ffi::Error {
+ code: rusqlite::ErrorCode::DatabaseBusy,
+ ..
+ })
+ | Some(rusqlite::ffi::Error {
+ code: rusqlite::ErrorCode::DatabaseLocked,
+ ..
+ }))
+ }
+
+ fn init_tables(&mut self) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
+ "CREATE TABLE IF NOT EXISTS profiles (
+ owner INTEGER,
+ alias BLOB,
+ profile BLOB,
+ UNIQUE(owner, alias));",
+ NO_PARAMS,
+ )
+ .context("Failed to initialize \"profiles\" table.")?;
+ Ok(())
+ })
+ }
+
+ fn list(&mut self, caller_uid: u32) -> Result<Vec<String>> {
+ self.with_transaction(TransactionBehavior::Deferred, |tx| {
+ let mut stmt = tx
+ .prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;")
+ .context("In list: Failed to prepare statement.")?;
+
+ let aliases = stmt
+ .query_map(params![caller_uid], |row| row.get(0))?
+ .collect::<rusqlite::Result<Vec<String>>>()
+ .context("In list: query_map failed.");
+ aliases
+ })
+ }
+
+ fn put(&mut self, caller_uid: u32, alias: &str, profile: &[u8]) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
+ "INSERT OR REPLACE INTO profiles (owner, alias, profile) values (?, ?, ?)",
+ params![caller_uid, alias, profile,],
+ )
+ .context("In put: Failed to insert or replace.")?;
+ Ok(())
+ })
+ }
+
+ fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ self.with_transaction(TransactionBehavior::Deferred, |tx| {
+ tx.query_row(
+ "SELECT profile FROM profiles WHERE owner = ? AND alias = ?;",
+ params![caller_uid, alias],
+ |row| row.get(0),
+ )
+ .optional()
+ .context("In get: failed loading profile.")
+ })
+ }
+
+ fn remove(&mut self, caller_uid: u32, alias: &str) -> Result<bool> {
+ let removed = self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
+ "DELETE FROM profiles WHERE owner = ? AND alias = ?;",
+ params![caller_uid, alias],
+ )
+ .context("In remove: Failed to delete row.")
+ })?;
+ Ok(removed == 1)
+ }
+}
+
+/// This is the main VpnProfileStore error type, it wraps binder exceptions and the
+/// VnpStore errors.
+#[derive(Debug, thiserror::Error, PartialEq)]
+pub enum Error {
+ /// Wraps a VpnProfileStore error code.
+ #[error("Error::Error({0:?})")]
+ Error(i32),
+ /// Wraps a Binder exception code other than a service specific exception.
+ #[error("Binder exception code {0:?}, {1:?}")]
+ Binder(ExceptionCode, i32),
+}
+
+impl Error {
+ /// Short hand for `Error::Error(ERROR_SYSTEM_ERROR)`
+ pub fn sys() -> Self {
+ Error::Error(ERROR_SYSTEM_ERROR)
+ }
+
+ /// Short hand for `Error::Error(ERROR_PROFILE_NOT_FOUND)`
+ pub fn not_found() -> Self {
+ Error::Error(ERROR_PROFILE_NOT_FOUND)
+ }
+}
+
+/// This function should be used by vpnprofilestore service calls to translate error conditions
+/// into service specific exceptions.
+///
+/// All error conditions get logged by this function.
+///
+/// `Error::Error(x)` variants get mapped onto a service specific error code of `x`.
+///
+/// All non `Error` error conditions get mapped onto `ERROR_SYSTEM_ERROR`.
+///
+/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
+/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
+/// typically returns Ok(value).
+fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
+where
+ F: FnOnce(U) -> BinderResult<T>,
+{
+ result.map_or_else(
+ |e| {
+ log::error!("{:#?}", e);
+ let root_cause = e.root_cause();
+ let rc = match root_cause.downcast_ref::<Error>() {
+ Some(Error::Error(e)) => *e,
+ Some(Error::Binder(_, _)) | None => ERROR_SYSTEM_ERROR,
+ };
+ Err(BinderStatus::new_service_specific_error(rc, None))
+ },
+ handle_ok,
+ )
+}
+
+/// Implements IVpnProfileStore AIDL interface.
+pub struct VpnProfileStore {
+ db_path: PathBuf,
+ async_task: AsyncTask,
+}
+
+struct AsyncState {
+ recently_imported: HashSet<(u32, String)>,
+ legacy_loader: LegacyBlobLoader,
+ db_path: PathBuf,
+}
+
+impl VpnProfileStore {
+ /// Creates a new VpnProfileStore instance.
+ pub fn new_native_binder(path: &Path) -> Strong<dyn IVpnProfileStore> {
+ let mut db_path = path.to_path_buf();
+ db_path.push("vpnprofilestore.sqlite");
+
+ let result = Self { db_path, async_task: Default::default() };
+ result.init_shelf(path);
+ BnVpnProfileStore::new_binder(result)
+ }
+
+ fn open_db(&self) -> Result<DB> {
+ DB::new(&self.db_path).context("In open_db: Failed to open db.")
+ }
+
+ fn get(&self, alias: &str) -> Result<Vec<u8>> {
+ let mut db = self.open_db().context("In get.")?;
+ let calling_uid = ThreadState::get_calling_uid();
+
+ if let Some(profile) =
+ db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
+ {
+ return Ok(profile);
+ }
+ if self.get_legacy(calling_uid, alias).context("In get: Trying to migrate legacy blob.")? {
+ // If we were able to migrate a legacy blob try again.
+ if let Some(profile) =
+ db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
+ {
+ return Ok(profile);
+ }
+ }
+ Err(Error::not_found()).context("In get: No such profile.")
+ }
+
+ fn put(&self, alias: &str, profile: &[u8]) -> Result<()> {
+ let calling_uid = ThreadState::get_calling_uid();
+ // In order to make sure that we don't have stale legacy profiles, make sure they are
+ // migrated before replacing them.
+ let _ = self.get_legacy(calling_uid, alias);
+ let mut db = self.open_db().context("In put.")?;
+ db.put(calling_uid, alias, profile).context("In put: Trying to insert profile into DB.")
+ }
+
+ fn remove(&self, alias: &str) -> Result<()> {
+ let calling_uid = ThreadState::get_calling_uid();
+ let mut db = self.open_db().context("In remove.")?;
+ // In order to make sure that we don't have stale legacy profiles, make sure they are
+ // migrated before removing them.
+ let _ = self.get_legacy(calling_uid, alias);
+ let removed = db
+ .remove(calling_uid, alias)
+ .context("In remove: Trying to remove profile from DB.")?;
+ if removed {
+ Ok(())
+ } else {
+ Err(Error::not_found()).context("In remove: No such profile.")
+ }
+ }
+
+ fn list(&self, prefix: &str) -> Result<Vec<String>> {
+ let mut db = self.open_db().context("In list.")?;
+ let calling_uid = ThreadState::get_calling_uid();
+ let mut result = self.list_legacy(calling_uid).context("In list.")?;
+ result
+ .append(&mut db.list(calling_uid).context("In list: Trying to get list of profiles.")?);
+ result = result.into_iter().filter(|s| s.starts_with(prefix)).collect();
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
+ }
+
+ fn init_shelf(&self, path: &Path) {
+ let mut db_path = path.to_path_buf();
+ self.async_task.queue_hi(move |shelf| {
+ let legacy_loader = LegacyBlobLoader::new(&db_path);
+ db_path.push("vpnprofilestore.sqlite");
+
+ shelf.put(AsyncState { legacy_loader, db_path, recently_imported: Default::default() });
+ })
+ }
+
+ fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Result<T>
+ where
+ F: FnOnce(&mut AsyncState) -> Result<T> + Send + 'static,
+ {
+ let (sender, receiver) = std::sync::mpsc::channel::<Result<T>>();
+ self.async_task.queue_hi(move |shelf| {
+ let state = shelf.get_downcast_mut::<AsyncState>().expect("Failed to get shelf.");
+ sender.send(f(state)).expect("Failed to send result.");
+ });
+ receiver.recv().context("In do_serialized: Failed to receive result.")?
+ }
+
+ fn list_legacy(&self, uid: u32) -> Result<Vec<String>> {
+ self.do_serialized(move |state| {
+ state
+ .legacy_loader
+ .list_vpn_profiles(uid)
+ .context("Trying to list legacy vnp profiles.")
+ })
+ .context("In list_legacy.")
+ }
+
+ fn get_legacy(&self, uid: u32, alias: &str) -> Result<bool> {
+ let alias = alias.to_string();
+ self.do_serialized(move |state| {
+ if state.recently_imported.contains(&(uid, alias.clone())) {
+ return Ok(true);
+ }
+ let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
+ let migrated =
+ Self::migrate_one_legacy_profile(uid, &alias, &state.legacy_loader, &mut db)
+ .context("Trying to migrate legacy vpn profile.")?;
+ if migrated {
+ state.recently_imported.insert((uid, alias));
+ }
+ Ok(migrated)
+ })
+ .context("In get_legacy.")
+ }
+
+ fn migrate_one_legacy_profile(
+ uid: u32,
+ alias: &str,
+ legacy_loader: &LegacyBlobLoader,
+ db: &mut DB,
+ ) -> Result<bool> {
+ let blob = legacy_loader
+ .read_vpn_profile(uid, alias)
+ .context("In migrate_one_legacy_profile: Trying to read legacy vpn profile.")?;
+ if let Some(profile) = blob {
+ db.put(uid, alias, &profile)
+ .context("In migrate_one_legacy_profile: Trying to insert profile into DB.")?;
+ legacy_loader
+ .remove_vpn_profile(uid, alias)
+ .context("In migrate_one_legacy_profile: Trying to delete legacy profile.")?;
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+}
+
+impl binder::Interface for VpnProfileStore {}
+
+impl IVpnProfileStore for VpnProfileStore {
+ fn get(&self, alias: &str) -> BinderResult<Vec<u8>> {
+ map_or_log_err(self.get(alias), Ok)
+ }
+ fn put(&self, alias: &str, profile: &[u8]) -> BinderResult<()> {
+ map_or_log_err(self.put(alias, profile), Ok)
+ }
+ fn remove(&self, alias: &str) -> BinderResult<()> {
+ map_or_log_err(self.remove(alias), Ok)
+ }
+ fn list(&self, prefix: &str) -> BinderResult<Vec<String>> {
+ map_or_log_err(self.list(prefix), Ok)
+ }
+}
+
+#[cfg(test)]
+mod db_test {
+ use super::*;
+ use keystore2_test_utils::TempDir;
+
+ static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
+ static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0];
+ static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
+ static TEST_BLOB4: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
+
+ #[test]
+ fn test_profile_db() {
+ let test_dir = TempDir::new("profiledb_test_").expect("Failed to create temp dir.");
+ let mut db =
+ DB::new(&test_dir.build().push("vpnprofile.sqlite")).expect("Failed to open database.");
+
+ // Insert three profiles for owner 2.
+ db.put(2, "test1", TEST_BLOB1).expect("Failed to insert test1.");
+ db.put(2, "test2", TEST_BLOB2).expect("Failed to insert test2.");
+ db.put(2, "test3", TEST_BLOB3).expect("Failed to insert test3.");
+
+ // Check list returns all inserted aliases.
+ assert_eq!(
+ vec!["test1".to_string(), "test2".to_string(), "test3".to_string(),],
+ db.list(2).expect("Failed to list profiles.")
+ );
+
+ // There should be no profiles for owner 1.
+ assert_eq!(Vec::<String>::new(), db.list(1).expect("Failed to list profiles."));
+
+ // Check the content of the three entries.
+ assert_eq!(
+ Some(TEST_BLOB1),
+ db.get(2, "test1").expect("Failed to get profile.").as_deref()
+ );
+ assert_eq!(
+ Some(TEST_BLOB2),
+ db.get(2, "test2").expect("Failed to get profile.").as_deref()
+ );
+ assert_eq!(
+ Some(TEST_BLOB3),
+ db.get(2, "test3").expect("Failed to get profile.").as_deref()
+ );
+
+ // Remove test2 and check and check that it is no longer retrievable.
+ assert!(db.remove(2, "test2").expect("Failed to remove profile."));
+ assert!(db.get(2, "test2").expect("Failed to get profile.").is_none());
+
+ // test2 should now no longer be in the list.
+ assert_eq!(
+ vec!["test1".to_string(), "test3".to_string(),],
+ db.list(2).expect("Failed to list profiles.")
+ );
+
+ // Put on existing alias replaces it.
+ // Verify test1 is TEST_BLOB1.
+ assert_eq!(
+ Some(TEST_BLOB1),
+ db.get(2, "test1").expect("Failed to get profile.").as_deref()
+ );
+ db.put(2, "test1", TEST_BLOB4).expect("Failed to replace test1.");
+ // Verify test1 is TEST_BLOB4.
+ assert_eq!(
+ Some(TEST_BLOB4),
+ db.get(2, "test1").expect("Failed to get profile.").as_deref()
+ );
+ }
+}
diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index 8da28f2..1c3706d 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -14,10 +14,18 @@
// List of clang-tidy checks that are reported as errors.
// Please keep this list ordered lexicographically.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
tidy_errors = [
"cert-err34-c",
"google-default-arguments",
- "google-explicit-constructor",
"google-runtime-int",
"google-runtime-member-string-references",
"misc-move-const-arg",
@@ -62,7 +70,7 @@
tidy_checks: tidy_errors,
tidy_checks_as_errors: tidy_errors,
tidy_flags: [
- "-format-style='file'",
+ "-format-style=file",
],
}
@@ -78,17 +86,22 @@
"CertUtils.cpp",
"Keymaster.cpp",
"KeymasterSigningKey.cpp",
+ "KeystoreKey.cpp",
"VerityUtils.cpp",
],
static_libs: [
"libmini_keyctl_static", // TODO need static?
"libc++fs",
+ "lib_odsign_proto",
],
shared_libs: [
"android.hardware.keymaster@4.1",
+ "android.system.keystore2-V1-cpp",
+ "android.hardware.security.keymint-V1-cpp",
"libbase",
+ "libbinder",
"libcrypto",
"libcrypto_utils",
"libfsverity",
@@ -97,6 +110,7 @@
"libkeymaster4support", // For authorization_set
"libkeymaster4_1support",
"libkeyutils",
+ "libprotobuf-cpp-full",
"libutils",
],
}
diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp
index 6b24391..cbd1942 100644
--- a/ondevice-signing/CertUtils.cpp
+++ b/ondevice-signing/CertUtils.cpp
@@ -25,6 +25,9 @@
#include <fcntl.h>
#include <vector>
+
+#include "KeyConstants.h"
+
const char kBasicConstraints[] = "CA:TRUE";
const char kKeyUsage[] = "critical,keyCertSign,cRLSign,digitalSignature";
const char kSubjectKeyIdentifier[] = "hash";
@@ -52,6 +55,33 @@
return true;
}
+Result<bssl::UniquePtr<RSA>> getRsa(const std::vector<uint8_t>& publicKey) {
+ bssl::UniquePtr<RSA> rsaPubkey(RSA_new());
+ rsaPubkey->n = BN_new();
+ rsaPubkey->e = BN_new();
+
+ BN_bin2bn(publicKey.data(), publicKey.size(), rsaPubkey->n);
+ BN_set_word(rsaPubkey->e, kRsaKeyExponent);
+
+ return rsaPubkey;
+}
+
+Result<void> verifySignature(const std::string& message, const std::string& signature,
+ const std::vector<uint8_t>& publicKey) {
+ auto rsaKey = getRsa(publicKey);
+ uint8_t hashBuf[SHA256_DIGEST_LENGTH];
+ SHA256(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(message.c_str())),
+ message.length(), hashBuf);
+
+ bool success = RSA_verify(NID_sha256, hashBuf, sizeof(hashBuf),
+ (const uint8_t*)signature.c_str(), signature.length(), rsaKey->get());
+
+ if (!success) {
+ return Error() << "Failed to verify signature.";
+ }
+ return {};
+}
+
Result<void> createSelfSignedCertificate(
const std::vector<uint8_t>& publicKey,
const std::function<Result<std::string>(const std::string&)>& signFunction,
@@ -66,8 +96,13 @@
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), kCertLifetimeSeconds);
- auto pubKeyData = publicKey.data();
- EVP_PKEY* public_key = d2i_PUBKEY(nullptr, &pubKeyData, publicKey.size());
+ // "publicKey" corresponds to the raw public key bytes - need to create
+ // a new RSA key with the correct exponent.
+ auto rsaPubkey = getRsa(publicKey);
+
+ EVP_PKEY* public_key = EVP_PKEY_new();
+ EVP_PKEY_assign_RSA(public_key, rsaPubkey->release());
+
if (!X509_set_pubkey(x509.get(), public_key)) {
return Error() << "Unable to set x509 public key";
}
@@ -117,6 +152,7 @@
i2d_X509_fp(f, x509.get());
fclose(f);
+ EVP_PKEY_free(public_key);
return {};
}
@@ -142,13 +178,25 @@
return pubKey;
}
-Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& keyData) {
+Result<std::vector<uint8_t>>
+extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& keyData) {
auto keyDataBytes = keyData.data();
EVP_PKEY* public_key = d2i_PUBKEY(nullptr, &keyDataBytes, keyData.size());
return extractPublicKey(public_key);
}
+Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& keyData) {
+ auto keyDataBytes = keyData.data();
+ bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &keyDataBytes, keyData.size()));
+ if (decoded_cert.get() == nullptr) {
+ return Error() << "Failed to decode X509 certificate.";
+ }
+ bssl::UniquePtr<EVP_PKEY> decoded_pkey(X509_get_pubkey(decoded_cert.get()));
+
+ return extractPublicKey(decoded_pkey.get());
+}
+
Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path) {
X509* cert;
auto f = fopen(path.c_str(), "r");
diff --git a/ondevice-signing/CertUtils.h b/ondevice-signing/CertUtils.h
index d9172d0..66dff04 100644
--- a/ondevice-signing/CertUtils.h
+++ b/ondevice-signing/CertUtils.h
@@ -25,5 +25,11 @@
android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData);
android::base::Result<std::vector<uint8_t>>
-extractPublicKeyFromX509(const std::vector<uint8_t>& path);
+extractPublicKeyFromX509(const std::vector<uint8_t>& x509);
+android::base::Result<std::vector<uint8_t>>
+extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& subjectKeyInfo);
android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path);
+
+android::base::Result<void> verifySignature(const std::string& message,
+ const std::string& signature,
+ const std::vector<uint8_t>& publicKey);
diff --git a/ondevice-signing/KeyConstants.h b/ondevice-signing/KeyConstants.h
new file mode 100644
index 0000000..9e1a513
--- /dev/null
+++ b/ondevice-signing/KeyConstants.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+static constexpr int kRsaKeySize = 2048;
+static constexpr int kRsaKeyExponent = 65537;
diff --git a/ondevice-signing/Keymaster.cpp b/ondevice-signing/Keymaster.cpp
index d43828a..6cfb565 100644
--- a/ondevice-signing/Keymaster.cpp
+++ b/ondevice-signing/Keymaster.cpp
@@ -66,9 +66,12 @@
}
}
+ if (devToUse == nullptr) {
+ LOG(WARNING) << "Didn't find a keymaster to use.";
+ }
mDevice = devToUse;
- return true;
+ return mDevice != nullptr;
}
std::optional<Keymaster> Keymaster::getInstance() {
diff --git a/ondevice-signing/KeymasterSigningKey.cpp b/ondevice-signing/KeymasterSigningKey.cpp
index f35f92b..dc3ef8a 100644
--- a/ondevice-signing/KeymasterSigningKey.cpp
+++ b/ondevice-signing/KeymasterSigningKey.cpp
@@ -33,45 +33,21 @@
using android::base::Result;
using android::base::unique_fd;
+const std::string kSigningKeyBlob = "/data/misc/odsign/key.blob";
+
KeymasterSigningKey::KeymasterSigningKey() {}
-Result<KeymasterSigningKey> KeymasterSigningKey::loadFromBlobAndVerify(const std::string& path) {
- KeymasterSigningKey signingKey;
+Result<std::unique_ptr<KeymasterSigningKey>>
+KeymasterSigningKey::loadFromBlobAndVerify(const std::string& path) {
+ auto signingKey = std::make_unique<KeymasterSigningKey>();
- auto status = signingKey.initializeFromKeyblob(path);
+ auto status = signingKey->initializeFromKeyblob(path);
if (!status.ok()) {
return status.error();
}
- return std::move(signingKey);
-}
-
-Result<KeymasterSigningKey> KeymasterSigningKey::createNewKey() {
- KeymasterSigningKey signingKey;
-
- auto status = signingKey.createSigningKey();
-
- if (!status.ok()) {
- return status.error();
- }
-
- return std::move(signingKey);
-}
-
-Result<void> KeymasterSigningKey::createSigningKey() {
- KeymasterSigningKey signingKey;
- mKeymaster = Keymaster::getInstance();
-
- auto keyBlob = mKeymaster->createKey();
-
- if (!keyBlob.ok()) {
- return keyBlob.error();
- }
-
- mVerifiedKeyBlob.assign(keyBlob->begin(), keyBlob->end());
-
- return {};
+ return signingKey;
}
Result<void> KeymasterSigningKey::saveKeyblob(const std::string& path) const {
@@ -89,31 +65,73 @@
}
}
-Result<std::vector<uint8_t>> KeymasterSigningKey::getPublicKey() const {
- auto publicKeyX509 = mKeymaster->extractPublicKey(mVerifiedKeyBlob);
- if (!publicKeyX509.ok()) {
- return publicKeyX509.error();
+Result<void> KeymasterSigningKey::createSigningKey() {
+ KeymasterSigningKey signingKey;
+ auto keymaster = Keymaster::getInstance();
+ if (!keymaster.has_value()) {
+ return Error() << "Failed to initialize keymaster.";
}
- return extractPublicKeyFromX509(publicKeyX509.value());
+ mKeymaster = keymaster;
+
+ auto keyBlob = mKeymaster->createKey();
+
+ if (!keyBlob.ok()) {
+ return keyBlob.error();
+ }
+
+ mVerifiedKeyBlob.assign(keyBlob->begin(), keyBlob->end());
+
+ return {};
}
-Result<void> KeymasterSigningKey::createX509Cert(const std::string& outPath) const {
- auto publicKey = mKeymaster->extractPublicKey(mVerifiedKeyBlob);
+Result<std::unique_ptr<KeymasterSigningKey>> KeymasterSigningKey::createAndPersistNewKey() {
+ auto signingKey = std::make_unique<KeymasterSigningKey>();
+ auto status = signingKey->createSigningKey();
+
+ if (!status.ok()) {
+ return status.error();
+ }
+
+ status = signingKey->saveKeyblob(kSigningKeyBlob);
+ if (!status.ok()) {
+ return status.error();
+ }
+
+ return signingKey;
+}
+
+Result<SigningKey*> KeymasterSigningKey::getInstance() {
+ auto key = loadFromBlobAndVerify(kSigningKeyBlob);
+
+ if (!key.ok()) {
+ key = createAndPersistNewKey();
+ if (!key.ok()) {
+ return key.error();
+ }
+ }
+
+ return key->release();
+}
+
+Result<std::vector<uint8_t>> KeymasterSigningKey::getPublicKey() const {
+ auto publicKey = mKeymaster->extractPublicKey(mVerifiedKeyBlob);
if (!publicKey.ok()) {
return publicKey.error();
}
- auto keymasterSignFunction = [&](const std::string& to_be_signed) {
- return this->sign(to_be_signed);
- };
- createSelfSignedCertificate(*publicKey, keymasterSignFunction, outPath);
- return {};
+ // Keymaster returns the public key not in a full X509 cert, but just the
+ // "SubjectPublicKeyInfo"
+ return extractPublicKeyFromSubjectPublicKeyInfo(publicKey.value());
}
Result<void> KeymasterSigningKey::initializeFromKeyblob(const std::string& path) {
- mKeymaster = Keymaster::getInstance();
std::string keyBlobData;
+ auto keymaster = Keymaster::getInstance();
+ if (!keymaster.has_value()) {
+ return Error() << "Failed to initialize keymaster.";
+ }
+ mKeymaster = keymaster;
bool result = ReadFileToString(path, &keyBlobData);
if (!result) {
diff --git a/ondevice-signing/KeymasterSigningKey.h b/ondevice-signing/KeymasterSigningKey.h
index 7631059..e66781f 100644
--- a/ondevice-signing/KeymasterSigningKey.h
+++ b/ondevice-signing/KeymasterSigningKey.h
@@ -23,30 +23,36 @@
#include <utils/StrongPointer.h>
#include "Keymaster.h"
+#include "SigningKey.h"
-class KeymasterSigningKey {
+class KeymasterSigningKey : public SigningKey {
using KmDevice = ::android::hardware::keymaster::V4_1::IKeymasterDevice;
public:
+ friend std::unique_ptr<KeymasterSigningKey> std::make_unique<KeymasterSigningKey>();
+ virtual ~KeymasterSigningKey(){};
+
// Allow the key to be moved around
KeymasterSigningKey& operator=(KeymasterSigningKey&& other) = default;
KeymasterSigningKey(KeymasterSigningKey&& other) = default;
- static android::base::Result<KeymasterSigningKey>
- loadFromBlobAndVerify(const std::string& path);
- static android::base::Result<KeymasterSigningKey> createNewKey();
+ static android::base::Result<SigningKey*> getInstance();
- /* Sign a message with an initialized signing key */
- android::base::Result<std::string> sign(const std::string& message) const;
- android::base::Result<void> saveKeyblob(const std::string& path) const;
- android::base::Result<std::vector<uint8_t>> getPublicKey() const;
- android::base::Result<void> createX509Cert(const std::string& path) const;
+ virtual android::base::Result<std::string> sign(const std::string& message) const;
+ virtual android::base::Result<std::vector<uint8_t>> getPublicKey() const;
private:
KeymasterSigningKey();
+ static android::base::Result<std::unique_ptr<KeymasterSigningKey>> createAndPersistNewKey();
+ static android::base::Result<std::unique_ptr<KeymasterSigningKey>>
+ loadFromBlobAndVerify(const std::string& path);
+
android::base::Result<void> createSigningKey();
android::base::Result<void> initializeFromKeyblob(const std::string& path);
+ android::base::Result<void> saveKeyblob(const std::string& path) const;
+
+ static android::base::Result<KeymasterSigningKey> createNewKey();
std::optional<Keymaster> mKeymaster;
std::vector<uint8_t> mVerifiedKeyBlob;
diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp
new file mode 100644
index 0000000..ea84183
--- /dev/null
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <binder/IServiceManager.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "CertUtils.h"
+#include "KeyConstants.h"
+#include "KeystoreKey.h"
+
+using android::defaultServiceManager;
+using android::IServiceManager;
+using android::sp;
+using android::String16;
+
+using android::hardware::security::keymint::Algorithm;
+using android::hardware::security::keymint::Digest;
+using android::hardware::security::keymint::KeyParameter;
+using android::hardware::security::keymint::KeyParameterValue;
+using android::hardware::security::keymint::KeyPurpose;
+using android::hardware::security::keymint::PaddingMode;
+using android::hardware::security::keymint::SecurityLevel;
+using android::hardware::security::keymint::Tag;
+
+using android::system::keystore2::CreateOperationResponse;
+using android::system::keystore2::Domain;
+using android::system::keystore2::KeyDescriptor;
+using android::system::keystore2::KeyEntryResponse;
+using android::system::keystore2::KeyMetadata;
+
+using android::base::Error;
+using android::base::Result;
+
+using android::base::unique_fd;
+
+static KeyDescriptor getKeyDescriptor() {
+ // AIDL parcelable objects don't have constructor
+ static KeyDescriptor descriptor;
+ static std::once_flag flag;
+ std::call_once(flag, [&]() {
+ descriptor.domain = Domain::SELINUX;
+ descriptor.alias = String16("ondevice-signing");
+ descriptor.nspace = 101; // odsign_key
+ });
+
+ return descriptor;
+}
+
+KeystoreKey::KeystoreKey() {}
+
+Result<KeyMetadata> KeystoreKey::createNewKey(const KeyDescriptor& descriptor) {
+ std::vector<KeyParameter> params;
+
+ KeyParameter algo;
+ algo.tag = Tag::ALGORITHM;
+ algo.value = KeyParameterValue::make<KeyParameterValue::algorithm>(Algorithm::RSA);
+ params.push_back(algo);
+
+ KeyParameter key_size;
+ key_size.tag = Tag::KEY_SIZE;
+ key_size.value = KeyParameterValue::make<KeyParameterValue::integer>(kRsaKeySize);
+ params.push_back(key_size);
+
+ KeyParameter digest;
+ digest.tag = Tag::DIGEST;
+ digest.value = KeyParameterValue::make<KeyParameterValue::digest>(Digest::SHA_2_256);
+ params.push_back(digest);
+
+ KeyParameter padding;
+ padding.tag = Tag::PADDING;
+ padding.value =
+ KeyParameterValue::make<KeyParameterValue::paddingMode>(PaddingMode::RSA_PKCS1_1_5_SIGN);
+ params.push_back(padding);
+
+ KeyParameter exponent;
+ exponent.tag = Tag::RSA_PUBLIC_EXPONENT;
+ exponent.value = KeyParameterValue::make<KeyParameterValue::longInteger>(kRsaKeyExponent);
+ params.push_back(exponent);
+
+ KeyParameter purpose;
+ purpose.tag = Tag::PURPOSE;
+ purpose.value = KeyParameterValue::make<KeyParameterValue::keyPurpose>(KeyPurpose::SIGN);
+ params.push_back(purpose);
+
+ KeyParameter auth;
+ auth.tag = Tag::NO_AUTH_REQUIRED;
+ auth.value = KeyParameterValue::make<KeyParameterValue::boolValue>(true);
+ params.push_back(auth);
+
+ KeyMetadata metadata;
+ auto status = mSecurityLevel->generateKey(descriptor, {}, params, 0, {}, &metadata);
+ if (!status.isOk()) {
+ return Error() << "Failed to create new key";
+ }
+
+ return metadata;
+}
+
+bool KeystoreKey::initialize() {
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm == nullptr) {
+ return false;
+ }
+ auto service = sm->getService(String16("android.system.keystore2"));
+ if (service == nullptr) {
+ return false;
+ }
+ mService = interface_cast<android::system::keystore2::IKeystoreService>(service);
+ if (mService == nullptr) {
+ return false;
+ }
+
+ auto status = mService->getSecurityLevel(SecurityLevel::STRONGBOX, &mSecurityLevel);
+ if (!status.isOk()) {
+ // TODO fallback to TEE
+ return false;
+ }
+
+ auto descriptor = getKeyDescriptor();
+ // See if we can fetch an existing key
+ KeyEntryResponse keyEntryResponse;
+ LOG(INFO) << "Trying to retrieve existing keystore key...";
+ status = mService->getKeyEntry(descriptor, &keyEntryResponse);
+ if (!status.isOk()) {
+ LOG(INFO) << "Existing keystore key not found, creating new key";
+ auto newKeyStatus = createNewKey(descriptor);
+ if (!newKeyStatus.ok()) {
+ LOG(ERROR) << "Failed to create new key";
+ return false;
+ }
+ mKeyMetadata = *newKeyStatus;
+ } else {
+ mKeyMetadata = keyEntryResponse.metadata;
+ }
+
+ LOG(ERROR) << "Initialized Keystore key.";
+ return true;
+}
+
+Result<SigningKey*> KeystoreKey::getInstance() {
+ static KeystoreKey keystoreKey;
+
+ if (!keystoreKey.initialize()) {
+ return Error() << "Failed to initialize keystore key.";
+ } else {
+ return &keystoreKey;
+ }
+}
+
+static std::vector<KeyParameter> getSignOpParameters() {
+ std::vector<KeyParameter> opParameters;
+
+ KeyParameter algo;
+ algo.tag = Tag::ALGORITHM;
+ algo.value = KeyParameterValue::make<KeyParameterValue::algorithm>(Algorithm::RSA);
+ opParameters.push_back(algo);
+
+ KeyParameter digest;
+ digest.tag = Tag::DIGEST;
+ digest.value = KeyParameterValue::make<KeyParameterValue::digest>(Digest::SHA_2_256);
+ opParameters.push_back(digest);
+
+ KeyParameter padding;
+ padding.tag = Tag::PADDING;
+ padding.value =
+ KeyParameterValue::make<KeyParameterValue::paddingMode>(PaddingMode::RSA_PKCS1_1_5_SIGN);
+ opParameters.push_back(padding);
+
+ KeyParameter purpose;
+ purpose.tag = Tag::PURPOSE;
+ purpose.value = KeyParameterValue::make<KeyParameterValue::keyPurpose>(KeyPurpose::SIGN);
+ opParameters.push_back(purpose);
+
+ return opParameters;
+}
+
+Result<std::string> KeystoreKey::sign(const std::string& message) const {
+ static auto opParameters = getSignOpParameters();
+
+ CreateOperationResponse opResponse;
+
+ auto status =
+ mSecurityLevel->createOperation(getKeyDescriptor(), opParameters, false, &opResponse);
+ if (!status.isOk()) {
+ return Error() << "Failed to create keystore signing operation: "
+ << status.serviceSpecificErrorCode();
+ }
+ auto operation = opResponse.iOperation;
+
+ std::optional<std::vector<uint8_t>> out;
+ status = operation->update({message.begin(), message.end()}, &out);
+ if (!status.isOk()) {
+ return Error() << "Failed to call keystore update operation.";
+ }
+
+ std::optional<std::vector<uint8_t>> signature;
+ status = operation->finish({}, {}, &signature);
+ if (!status.isOk()) {
+ return Error() << "Failed to call keystore finish operation.";
+ }
+
+ if (!signature.has_value()) {
+ return Error() << "Didn't receive a signature from keystore finish operation.";
+ }
+
+ std::string result{signature.value().begin(), signature.value().end()};
+
+ return result;
+}
+
+Result<std::vector<uint8_t>> KeystoreKey::getPublicKey() const {
+ return extractPublicKeyFromX509(mKeyMetadata.certificate.value());
+}
diff --git a/ondevice-signing/KeystoreKey.h b/ondevice-signing/KeystoreKey.h
new file mode 100644
index 0000000..6b9cb57
--- /dev/null
+++ b/ondevice-signing/KeystoreKey.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include <android-base/macros.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+#include <utils/StrongPointer.h>
+
+#include <android/system/keystore2/IKeystoreService.h>
+
+#include "SigningKey.h"
+
+class KeystoreKey : public SigningKey {
+ using IKeystoreService = ::android::system::keystore2::IKeystoreService;
+ using IKeystoreSecurityLevel = ::android::system::keystore2::IKeystoreSecurityLevel;
+ using KeyDescriptor = ::android::system::keystore2::KeyDescriptor;
+ using KeyMetadata = ::android::system::keystore2::KeyMetadata;
+
+ public:
+ virtual ~KeystoreKey(){};
+ static android::base::Result<SigningKey*> getInstance();
+
+ virtual android::base::Result<std::string> sign(const std::string& message) const;
+ virtual android::base::Result<std::vector<uint8_t>> getPublicKey() const;
+
+ private:
+ KeystoreKey();
+ bool initialize();
+ android::base::Result<KeyMetadata> createNewKey(const KeyDescriptor& descriptor);
+
+ android::sp<IKeystoreService> mService;
+ android::sp<IKeystoreSecurityLevel> mSecurityLevel;
+ KeyMetadata mKeyMetadata;
+};
diff --git a/ondevice-signing/SigningKey.h b/ondevice-signing/SigningKey.h
new file mode 100644
index 0000000..89294fc
--- /dev/null
+++ b/ondevice-signing/SigningKey.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <android-base/macros.h>
+#include <android-base/result.h>
+
+class SigningKey {
+ public:
+ virtual ~SigningKey(){};
+ /* Sign a message with an initialized signing key */
+ virtual android::base::Result<std::string> sign(const std::string& message) const = 0;
+ /* Retrieve the associated public key */
+ virtual android::base::Result<std::vector<uint8_t>> getPublicKey() const = 0;
+};
diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp
index 579d3d8..71ba8f6 100644
--- a/ondevice-signing/VerityUtils.cpp
+++ b/ondevice-signing/VerityUtils.cpp
@@ -15,12 +15,14 @@
*/
#include <filesystem>
+#include <map>
#include <string>
#include <fcntl.h>
#include <linux/fs.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
@@ -28,13 +30,17 @@
#include <linux/fsverity.h>
#include "CertUtils.h"
-#include "KeymasterSigningKey.h"
+#include "SigningKey.h"
+
+#define FS_VERITY_MAX_DIGEST_SIZE 64
using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
using android::base::unique_fd;
+static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
+
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define cpu_to_le16(v) ((__force __le16)(uint16_t)(v))
#define le16_to_cpu(v) ((__force uint16_t)(__le16)(v))
@@ -50,13 +56,21 @@
__u8 digest[];
};
+static std::string toHex(const std::vector<uint8_t>& data) {
+ std::stringstream ss;
+ for (auto it = data.begin(); it != data.end(); ++it) {
+ ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
+ }
+ return ss.str();
+}
+
static int read_callback(void* file, void* buf, size_t count) {
int* fd = (int*)file;
if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO;
return 0;
}
-static Result<std::vector<uint8_t>> createDigest(const std::string& path) {
+Result<std::vector<uint8_t>> createDigest(const std::string& path) {
struct stat filestat;
unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
@@ -74,11 +88,12 @@
return std::vector<uint8_t>(&digest->digest[0], &digest->digest[32]);
}
-static Result<std::vector<uint8_t>> signDigest(const KeymasterSigningKey& key,
+static Result<std::vector<uint8_t>> signDigest(const SigningKey& key,
const std::vector<uint8_t>& digest) {
- struct fsverity_signed_digest* d = NULL;
+ fsverity_signed_digest* d;
size_t signed_digest_size = sizeof(*d) + digest.size();
- d = (struct fsverity_signed_digest*)malloc(signed_digest_size);
+ std::unique_ptr<uint8_t[]> digest_buffer{new uint8_t[signed_digest_size]};
+ d = (fsverity_signed_digest*)digest_buffer.get();
memcpy(d->magic, "FSVerity", 8);
d->digest_algorithm = cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
@@ -93,7 +108,7 @@
return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end());
}
-Result<void> enableFsVerity(const std::string& path, const KeymasterSigningKey& key) {
+Result<std::string> enableFsVerity(const std::string& path, const SigningKey& key) {
auto digest = createDigest(path);
if (!digest.ok()) {
return digest.error();
@@ -120,10 +135,13 @@
return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY on " << path;
}
- return {};
+ // Return the root hash as a hex string
+ return toHex(digest.value());
}
-Result<void> addFilesToVerityRecursive(const std::string& path, const KeymasterSigningKey& key) {
+Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path,
+ const SigningKey& key) {
+ std::map<std::string, std::string> digests;
std::error_code ec;
auto it = std::filesystem::recursive_directory_iterator(path, ec);
@@ -136,14 +154,18 @@
if (!result.ok()) {
return result.error();
}
+ digests[it->path()] = *result;
}
++it;
}
+ if (ec) {
+ return Error() << "Failed to iterate " << path << ": " << ec;
+ }
- return {};
+ return digests;
}
-Result<bool> isFileInVerity(const std::string& path) {
+Result<std::string> isFileInVerity(const std::string& path) {
unsigned int flags;
unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
@@ -155,11 +177,24 @@
if (ret < 0) {
return ErrnoError() << "Failed to FS_IOC_GETFLAGS for " << path;
}
+ if (!(flags & FS_VERITY_FL)) {
+ return Error() << "File is not in fs-verity: " << path;
+ }
- return (flags & FS_VERITY_FL);
+ struct fsverity_digest* d;
+ d = (struct fsverity_digest*)malloc(sizeof(*d) + FS_VERITY_MAX_DIGEST_SIZE);
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+ ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d);
+ if (ret < 0) {
+ return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY for " << path;
+ }
+ std::vector<uint8_t> digest_vector(&d->digest[0], &d->digest[d->digest_size]);
+
+ return toHex(digest_vector);
}
-Result<void> verifyAllFilesInVerity(const std::string& path) {
+Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) {
+ std::map<std::string, std::string> digests;
std::error_code ec;
auto it = std::filesystem::recursive_directory_iterator(path, ec);
@@ -172,12 +207,50 @@
if (!result.ok()) {
return result.error();
}
- if (!*result) {
- return Error() << "File " << it->path() << " not in fs-verity";
- }
+ digests[it->path()] = *result;
} // TODO reject other types besides dirs?
++it;
}
+ if (ec) {
+ return Error() << "Failed to iterate " << path << ": " << ec;
+ }
+
+ return digests;
+}
+
+Result<void> addCertToFsVerityKeyring(const std::string& path) {
+ const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", "fsv_ods"};
+
+ int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+ pid_t pid = fork();
+ if (pid == 0) {
+ dup2(fd, STDIN_FILENO);
+ close(fd);
+ int argc = arraysize(argv);
+ char* argv_child[argc + 1];
+ memcpy(argv_child, argv, argc * sizeof(char*));
+ argv_child[argc] = nullptr;
+ execvp(argv_child[0], const_cast<char**>(argv_child));
+ PLOG(ERROR) << "exec in ForkExecvp";
+ _exit(EXIT_FAILURE);
+ } else {
+ close(fd);
+ }
+ if (pid == -1) {
+ return ErrnoError() << "Failed to fork.";
+ }
+ int status;
+ if (waitpid(pid, &status, 0) == -1) {
+ return ErrnoError() << "waitpid() failed.";
+ }
+ if (!WIFEXITED(status)) {
+ return Error() << kFsVerityInitPath << ": abnormal process exit";
+ }
+ if (WEXITSTATUS(status)) {
+ if (status != 0) {
+ return Error() << kFsVerityInitPath << " exited with " << status;
+ }
+ }
return {};
}
diff --git a/ondevice-signing/VerityUtils.h b/ondevice-signing/VerityUtils.h
index 1eca5a6..84af319 100644
--- a/ondevice-signing/VerityUtils.h
+++ b/ondevice-signing/VerityUtils.h
@@ -18,8 +18,11 @@
#include <android-base/result.h>
-#include "KeymasterSigningKey.h"
+#include "SigningKey.h"
-android::base::Result<void> verifyAllFilesInVerity(const std::string& path);
-android::base::Result<void> addFilesToVerityRecursive(const std::string& path,
- const KeymasterSigningKey& key);
+android::base::Result<void> addCertToFsVerityKeyring(const std::string& path);
+android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path);
+android::base::Result<std::map<std::string, std::string>>
+verifyAllFilesInVerity(const std::string& path);
+android::base::Result<std::map<std::string, std::string>>
+addFilesToVerityRecursive(const std::string& path, const SigningKey& key);
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
index efe7d35..eeef868 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -16,11 +16,12 @@
#include <fcntl.h>
#include <filesystem>
+#include <fstream>
#include <iomanip>
#include <iostream>
+#include <iterator>
#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/wait.h>
#include <unistd.h>
#include <android-base/file.h>
@@ -30,68 +31,32 @@
#include "CertUtils.h"
#include "KeymasterSigningKey.h"
+#include "KeystoreKey.h"
#include "VerityUtils.h"
+#include "odsign_info.pb.h"
+
using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
+using OdsignInfo = ::odsign::proto::OdsignInfo;
+
const std::string kSigningKeyBlob = "/data/misc/odsign/key.blob";
const std::string kSigningKeyCert = "/data/misc/odsign/key.cert";
+const std::string kOdsignInfo = "/data/misc/odsign/odsign.info";
+const std::string kOdsignInfoSignature = "/data/misc/odsign/odsign.info.signature";
-const std::string kArtArtifactsDir = "/data/misc/apexdata/com.android.art/system";
+const std::string kArtArtifactsDir = "/data/misc/apexdata/com.android.art/dalvik-cache";
static const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh";
-static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
+static const char* kFsVerityProcPath = "/proc/sys/fs/verity";
static const bool kForceCompilation = false;
+static const bool kUseKeystore = false;
-Result<void> addCertToFsVerityKeyring(const std::string& path) {
- const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", "fsv_ods"};
-
- int fd = open(path.c_str(), O_RDONLY);
- pid_t pid = fork();
- if (pid == 0) {
- dup2(fd, STDIN_FILENO);
- close(fd);
- int argc = arraysize(argv);
- char* argv_child[argc + 1];
- memcpy(argv_child, argv, argc * sizeof(char*));
- argv_child[argc] = nullptr;
- execvp(argv_child[0], const_cast<char**>(argv_child));
- PLOG(ERROR) << "exec in ForkExecvp";
- _exit(EXIT_FAILURE);
- } else {
- close(fd);
- }
- if (pid == -1) {
- return ErrnoError() << "Failed to fork.";
- }
- int status;
- if (waitpid(pid, &status, 0) == -1) {
- return ErrnoError() << "waitpid() failed.";
- }
- if (!WIFEXITED(status)) {
- return Error() << kFsVerityInitPath << ": abnormal process exit";
- }
- if (WEXITSTATUS(status)) {
- if (status != 0) {
- return Error() << kFsVerityInitPath << " exited with " << status;
- }
- }
-
- return {};
-}
-
-Result<KeymasterSigningKey> loadAndVerifyExistingKey() {
- if (access(kSigningKeyBlob.c_str(), F_OK) < 0) {
- return ErrnoError() << "Key blob not found: " << kSigningKeyBlob;
- }
- return KeymasterSigningKey::loadFromBlobAndVerify(kSigningKeyBlob);
-}
-
-Result<void> verifyAndLoadExistingCert(const KeymasterSigningKey& key) {
+Result<void> verifyExistingCert(const SigningKey& key) {
if (access(kSigningKeyCert.c_str(), F_OK) < 0) {
return ErrnoError() << "Key certificate not found: " << kSigningKeyCert;
}
@@ -109,28 +74,22 @@
<< " does not match signing public key.";
}
- auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
- if (!cert_add_result.ok()) {
- return cert_add_result.error();
- }
-
// At this point, we know the cert matches
return {};
}
-Result<KeymasterSigningKey> createAndPersistKey(const std::string& path) {
- auto key = KeymasterSigningKey::createNewKey();
+Result<void> createX509Cert(const SigningKey& key, const std::string& outPath) {
+ auto publicKey = key.getPublicKey();
- if (!key.ok()) {
- return key.error();
+ if (!publicKey.ok()) {
+ return publicKey.error();
}
- auto result = key->saveKeyblob(path);
- if (!result.ok()) {
- return result.error();
- }
-
- return key;
+ auto keymasterSignFunction = [&](const std::string& to_be_signed) {
+ return key.sign(to_be_signed);
+ };
+ createSelfSignedCertificate(*publicKey, keymasterSignFunction, outPath);
+ return {};
}
bool compileArtifacts(bool force) {
@@ -147,43 +106,192 @@
0;
}
+static std::string toHex(const std::vector<uint8_t>& digest) {
+ std::stringstream ss;
+ for (auto it = digest.begin(); it != digest.end(); ++it) {
+ ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
+ }
+ return ss.str();
+}
+
+Result<std::map<std::string, std::string>> computeDigests(const std::string& path) {
+ std::error_code ec;
+ std::map<std::string, std::string> digests;
+
+ auto it = std::filesystem::recursive_directory_iterator(path, ec);
+ auto end = std::filesystem::recursive_directory_iterator();
+
+ while (!ec && it != end) {
+ if (it->is_regular_file()) {
+ auto digest = createDigest(it->path());
+ if (!digest.ok()) {
+ return Error() << "Failed to compute digest for " << it->path();
+ }
+ digests[it->path()] = toHex(*digest);
+ }
+ ++it;
+ }
+ if (ec) {
+ return Error() << "Failed to iterate " << path << ": " << ec;
+ }
+
+ return digests;
+}
+
+Result<void> verifyDigests(const std::map<std::string, std::string>& digests,
+ const std::map<std::string, std::string>& trusted_digests) {
+ for (const auto& path_digest : digests) {
+ auto path = path_digest.first;
+ auto digest = path_digest.second;
+ if ((trusted_digests.count(path) == 0)) {
+ return Error() << "Couldn't find digest for " << path;
+ }
+ if (trusted_digests.at(path) != digest) {
+ return Error() << "Digest mismatch for " << path;
+ }
+ }
+
+ // All digests matched!
+ if (digests.size() > 0) {
+ LOG(INFO) << "All root hashes match.";
+ }
+ return {};
+}
+
+Result<void> verifyIntegrityFsVerity(const std::map<std::string, std::string>& trusted_digests) {
+ // Just verify that the files are in verity, and get their digests
+ auto result = verifyAllFilesInVerity(kArtArtifactsDir);
+ if (!result.ok()) {
+ return result.error();
+ }
+
+ return verifyDigests(*result, trusted_digests);
+}
+
+Result<void> verifyIntegrityNoFsVerity(const std::map<std::string, std::string>& trusted_digests) {
+ // On these devices, just compute the digests, and verify they match the ones we trust
+ auto result = computeDigests(kArtArtifactsDir);
+ if (!result.ok()) {
+ return result.error();
+ }
+
+ return verifyDigests(*result, trusted_digests);
+}
+
+Result<OdsignInfo> getOdsignInfo(const SigningKey& key) {
+ std::string persistedSignature;
+ OdsignInfo odsignInfo;
+
+ if (!android::base::ReadFileToString(kOdsignInfoSignature, &persistedSignature)) {
+ return ErrnoError() << "Failed to read " << kOdsignInfoSignature;
+ }
+
+ std::fstream odsign_info(kOdsignInfo, std::ios::in | std::ios::binary);
+ if (!odsign_info) {
+ return Error() << "Failed to open " << kOdsignInfo;
+ }
+ odsign_info.seekg(0);
+ // Verify the hash
+ std::string odsign_info_str((std::istreambuf_iterator<char>(odsign_info)),
+ std::istreambuf_iterator<char>());
+
+ auto publicKey = key.getPublicKey();
+ auto signResult = verifySignature(odsign_info_str, persistedSignature, *publicKey);
+ if (!signResult.ok()) {
+ return Error() << kOdsignInfoSignature << " does not match.";
+ } else {
+ LOG(INFO) << kOdsignInfoSignature << " matches.";
+ }
+
+ odsign_info.seekg(0);
+ if (!odsignInfo.ParseFromIstream(&odsign_info)) {
+ return Error() << "Failed to parse " << kOdsignInfo;
+ }
+
+ LOG(INFO) << "Loaded " << kOdsignInfo;
+ return odsignInfo;
+}
+
+Result<void> persistDigests(const std::map<std::string, std::string>& digests,
+ const SigningKey& key) {
+ OdsignInfo signInfo;
+ google::protobuf::Map<std::string, std::string> proto_hashes(digests.begin(), digests.end());
+ auto map = signInfo.mutable_file_hashes();
+ *map = proto_hashes;
+
+ std::fstream odsign_info(kOdsignInfo,
+ std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!signInfo.SerializeToOstream(&odsign_info)) {
+ return Error() << "Failed to persist root hashes in " << kOdsignInfo;
+ }
+
+ // Sign the signatures with our key itself, and write that to storage
+ odsign_info.seekg(0, std::ios::beg);
+ std::string odsign_info_str((std::istreambuf_iterator<char>(odsign_info)),
+ std::istreambuf_iterator<char>());
+ auto signResult = key.sign(odsign_info_str);
+ if (!signResult.ok()) {
+ return Error() << "Failed to sign " << kOdsignInfo;
+ }
+ android::base::WriteStringToFile(*signResult, kOdsignInfoSignature);
+ return {};
+}
+
int main(int /* argc */, char** /* argv */) {
- auto removeArtifacts = []() {
+ auto removeArtifacts = []() -> std::uintmax_t {
std::error_code ec;
auto num_removed = std::filesystem::remove_all(kArtArtifactsDir, ec);
if (ec) {
// TODO can't remove artifacts, signal Zygote shouldn't use them
LOG(ERROR) << "Can't remove " << kArtArtifactsDir << ": " << ec.message();
+ return 0;
} else {
- LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir;
+ if (num_removed > 0) {
+ LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir;
+ }
+ return num_removed;
}
};
// Make sure we delete the artifacts in all early (error) exit paths
auto scope_guard = android::base::make_scope_guard(removeArtifacts);
- auto key = loadAndVerifyExistingKey();
- if (!key.ok()) {
- LOG(WARNING) << key.error().message();
-
- key = createAndPersistKey(kSigningKeyBlob);
- if (!key.ok()) {
- LOG(ERROR) << "Failed to create or persist new key: " << key.error().message();
+ SigningKey* key;
+ if (kUseKeystore) {
+ auto keystoreResult = KeystoreKey::getInstance();
+ if (!keystoreResult.ok()) {
+ LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error().message();
return -1;
}
+ key = keystoreResult.value();
} else {
- LOG(INFO) << "Found and verified existing key: " << kSigningKeyBlob;
+ // TODO - keymaster will go away
+ auto keymasterResult = KeymasterSigningKey::getInstance();
+ if (!keymasterResult.ok()) {
+ LOG(ERROR) << "Failed to create keymaster key: " << keymasterResult.error().message();
+ return -1;
+ }
+ key = keymasterResult.value();
}
- auto existing_cert = verifyAndLoadExistingCert(key.value());
- if (!existing_cert.ok()) {
- LOG(WARNING) << existing_cert.error().message();
+ bool supportsFsVerity = access(kFsVerityProcPath, F_OK) == 0;
+ if (!supportsFsVerity) {
+ LOG(INFO) << "Device doesn't support fsverity. Falling back to full verification.";
+ }
- // Try to create a new cert
- auto new_cert = key->createX509Cert(kSigningKeyCert);
- if (!new_cert.ok()) {
- LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error().message();
- // TODO apparently the key become invalid - delete the blob / cert
- return -1;
+ if (supportsFsVerity) {
+ auto existing_cert = verifyExistingCert(*key);
+ if (!existing_cert.ok()) {
+ LOG(WARNING) << existing_cert.error().message();
+
+ // Try to create a new cert
+ auto new_cert = createX509Cert(*key, kSigningKeyCert);
+ if (!new_cert.ok()) {
+ LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error().message();
+ // TODO apparently the key become invalid - delete the blob / cert
+ return -1;
+ }
+ } else {
+ LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert;
}
auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
if (!cert_add_result.ok()) {
@@ -191,29 +299,58 @@
<< cert_add_result.error().message();
return -1;
}
+ }
+
+ auto signInfo = getOdsignInfo(*key);
+ if (!signInfo.ok()) {
+ int num_removed = removeArtifacts();
+ // Only a warning if there were artifacts to begin with, which suggests tampering or
+ // corruption
+ if (num_removed > 0) {
+ LOG(WARNING) << signInfo.error().message();
+ }
} else {
- LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert;
+ std::map<std::string, std::string> trusted_digests(signInfo->file_hashes().begin(),
+ signInfo->file_hashes().end());
+ Result<void> integrityStatus;
+
+ if (supportsFsVerity) {
+ integrityStatus = verifyIntegrityFsVerity(trusted_digests);
+ } else {
+ integrityStatus = verifyIntegrityNoFsVerity(trusted_digests);
+ }
+ if (!integrityStatus.ok()) {
+ LOG(WARNING) << integrityStatus.error().message() << ", removing " << kArtArtifactsDir;
+ removeArtifacts();
+ }
}
- auto verityStatus = verifyAllFilesInVerity(kArtArtifactsDir);
- if (!verityStatus.ok()) {
- LOG(WARNING) << verityStatus.error().message() << ", removing " << kArtArtifactsDir;
- removeArtifacts();
- }
-
+ // Ask ART whether it considers the artifacts valid
+ LOG(INFO) << "Asking odrefresh to verify artifacts (if present)...";
bool artifactsValid = validateArtifacts();
+ LOG(INFO) << "odrefresh said they are " << (artifactsValid ? "VALID" : "INVALID");
if (!artifactsValid || kForceCompilation) {
- removeArtifacts();
-
LOG(INFO) << "Starting compilation... ";
bool ret = compileArtifacts(kForceCompilation);
LOG(INFO) << "Compilation done, returned " << ret;
- verityStatus = addFilesToVerityRecursive(kArtArtifactsDir, key.value());
+ Result<std::map<std::string, std::string>> digests;
+ if (supportsFsVerity) {
+ digests = addFilesToVerityRecursive(kArtArtifactsDir, *key);
+ } else {
+ // If we can't use verity, just compute the root hashes and store
+ // those, so we can reverify them at the next boot.
+ digests = computeDigests(kArtArtifactsDir);
+ }
+ if (!digests.ok()) {
+ LOG(ERROR) << digests.error().message();
+ return -1;
+ }
- if (!verityStatus.ok()) {
- LOG(ERROR) << "Failed to add " << verityStatus.error().message();
+ auto persistStatus = persistDigests(*digests, *key);
+ if (!persistStatus.ok()) {
+ LOG(ERROR) << persistStatus.error().message();
return -1;
}
}
diff --git a/ondevice-signing/proto/Android.bp b/ondevice-signing/proto/Android.bp
new file mode 100644
index 0000000..fd48f31
--- /dev/null
+++ b/ondevice-signing/proto/Android.bp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "lib_odsign_proto",
+ host_supported: true,
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ },
+ srcs: ["odsign_info.proto"],
+}
diff --git a/ondevice-signing/proto/odsign_info.proto b/ondevice-signing/proto/odsign_info.proto
new file mode 100644
index 0000000..9d49c6c
--- /dev/null
+++ b/ondevice-signing/proto/odsign_info.proto
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+syntax = "proto3";
+
+package odsign.proto;
+
+message OdsignInfo {
+ // Map of artifact files to their hashes
+ map<string, string> file_hashes = 1;
+}
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index c1c8d15..d3f06fe 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -14,6 +14,15 @@
// limitations under the License.
//
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
aidl_interface {
name: "android.security.provisioner",
unstable: true,