Merge "Adding the ability to delete all attestation keys"
diff --git a/identity/Android.bp b/identity/Android.bp
index ed8ff2f..5841bf6 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -39,6 +39,7 @@
shared_libs: [
"libbase",
"libbinder",
+ "libbinder_ndk",
"libkeystore_aidl",
"libcredstore_aidl",
"libutils",
@@ -46,6 +47,9 @@
"android.hardware.identity-support-lib",
"libkeymaster4support",
"libkeystore-attestation-application-id",
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.security.authorization-ndk_platform",
+ "PlatformProperties",
],
static_libs: [
"android.hardware.identity-V3-cpp",
diff --git a/identity/Credential.cpp b/identity/Credential.cpp
index a3c72ed..23e144f 100644
--- a/identity/Credential.cpp
+++ b/identity/Credential.cpp
@@ -17,7 +17,7 @@
#define LOG_TAG "Credential"
#include <android-base/logging.h>
-
+#include <android/binder_manager.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <android/security/identity/ICredentialStore.h>
@@ -33,6 +33,12 @@
#include <future>
#include <tuple>
+#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
+#include <aidl/android/hardware/security/secureclock/TimeStampToken.h>
+#include <aidl/android/security/authorization/AuthorizationTokens.h>
+#include <aidl/android/security/authorization/IKeystoreAuthorization.h>
+#include <android/sysprop/Keystore2Properties.sysprop.h>
+
#include "Credential.h"
#include "CredentialData.h"
#include "Util.h"
@@ -47,6 +53,7 @@
using std::tuple;
using android::security::keystore::IKeystoreService;
+using namespace android::sysprop;
using ::android::hardware::identity::IWritableIdentityCredential;
@@ -55,11 +62,17 @@
using ::android::hardware::identity::support::ecKeyPairGetPublicKey;
using ::android::hardware::identity::support::sha256;
+using android::hardware::keymaster::SecurityLevel;
using android::hardware::keymaster::V4_0::HardwareAuthToken;
using android::hardware::keymaster::V4_0::VerificationToken;
using AidlHardwareAuthToken = android::hardware::keymaster::HardwareAuthToken;
using AidlVerificationToken = android::hardware::keymaster::VerificationToken;
+using KeyMintAuthToken = ::aidl::android::hardware::security::keymint::HardwareAuthToken;
+using ::aidl::android::hardware::security::secureclock::TimeStampToken;
+using ::aidl::android::security::authorization::AuthorizationTokens;
+using ::aidl::android::security::authorization::IKeystoreAuthorization;
+
Credential::Credential(CipherSuite cipherSuite, const std::string& dataPath,
const std::string& credentialName, uid_t callingUid,
HardwareInformation hwInfo, sp<IIdentityCredentialStore> halStoreBinder,
@@ -168,9 +181,10 @@
// Returns false if an error occurred communicating with keystore.
//
-bool getTokensFromKeystore(uint64_t challenge, uint64_t secureUserId,
- unsigned int authTokenMaxAgeMillis, vector<uint8_t>& authToken,
- vector<uint8_t>& verificationToken) {
+bool getTokensFromKeystore1(uint64_t challenge, uint64_t secureUserId,
+ unsigned int authTokenMaxAgeMillis,
+ AidlHardwareAuthToken& aidlAuthToken,
+ AidlVerificationToken& aidlVerificationToken) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("android.security.keystore"));
sp<IKeystoreService> keystore = interface_cast<IKeystoreService>(binder);
@@ -189,19 +203,100 @@
auto fstatus = future.wait_for(std::chrono::milliseconds(5000));
if (fstatus != std::future_status::ready) {
- LOG(ERROR) << "Waited 5 seconds from tokens for credstore, aborting";
+ LOG(ERROR) << "Waited 5 seconds for tokens from keystore, aborting";
return false;
}
auto [success, returnedAuthToken, returnedVerificationToken] = future.get();
if (!success) {
- LOG(ERROR) << "Error getting tokens from credstore";
+ LOG(ERROR) << "Error getting tokens from keystore1";
return false;
}
- authToken = returnedAuthToken;
- verificationToken = returnedVerificationToken;
+ // It's entirely possible getTokensFromKeystore() succeeded but didn't
+ // return any tokens (in which case the returned byte-vectors are
+ // empty). For example, this can happen if no auth token is available
+ // which satifies e.g. |authTokenMaxAgeMillis|.
+ //
+ if (returnedAuthToken.size() > 0) {
+ HardwareAuthToken authToken =
+ android::hardware::keymaster::V4_0::support::hidlVec2AuthToken(returnedAuthToken);
+ // Convert from HIDL to AIDL...
+ aidlAuthToken.challenge = int64_t(authToken.challenge);
+ aidlAuthToken.userId = int64_t(authToken.userId);
+ aidlAuthToken.authenticatorId = int64_t(authToken.authenticatorId);
+ aidlAuthToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType(
+ int32_t(authToken.authenticatorType));
+ aidlAuthToken.timestamp.milliSeconds = int64_t(authToken.timestamp);
+ aidlAuthToken.mac = authToken.mac;
+ }
+
+ if (returnedVerificationToken.size() > 0) {
+ optional<VerificationToken> token =
+ android::hardware::keymaster::V4_0::support::deserializeVerificationToken(
+ returnedVerificationToken);
+ if (!token) {
+ LOG(ERROR) << "Error deserializing verification token";
+ }
+ aidlVerificationToken.challenge = token->challenge;
+ aidlVerificationToken.timestamp.milliSeconds = token->timestamp;
+ aidlVerificationToken.securityLevel =
+ ::android::hardware::keymaster::SecurityLevel(token->securityLevel);
+ aidlVerificationToken.mac = token->mac;
+ }
return true;
}
+// Returns false if an error occurred communicating with keystore.
+//
+bool getTokensFromKeystore2(uint64_t challenge, uint64_t secureUserId,
+ unsigned int authTokenMaxAgeMillis,
+ AidlHardwareAuthToken& aidlAuthToken,
+ AidlVerificationToken& aidlVerificationToken) {
+ // try to connect to IKeystoreAuthorization AIDL service first.
+ AIBinder* authzAIBinder = AServiceManager_checkService("android.security.authorization");
+ ::ndk::SpAIBinder authzBinder(authzAIBinder);
+ auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
+ if (authzService) {
+ AuthorizationTokens authzTokens;
+ auto result = authzService->getAuthTokensForCredStore(challenge, secureUserId,
+ authTokenMaxAgeMillis, &authzTokens);
+ // Convert KeyMint auth token to KeyMaster authtoken, only if tokens are
+ // returned
+ if (result.isOk()) {
+ KeyMintAuthToken keymintAuthToken = authzTokens.authToken;
+ aidlAuthToken.challenge = keymintAuthToken.challenge;
+ aidlAuthToken.userId = keymintAuthToken.userId;
+ aidlAuthToken.authenticatorId = keymintAuthToken.authenticatorId;
+ aidlAuthToken.authenticatorType =
+ ::android::hardware::keymaster::HardwareAuthenticatorType(
+ int32_t(keymintAuthToken.authenticatorType));
+ aidlAuthToken.timestamp.milliSeconds = keymintAuthToken.timestamp.milliSeconds;
+ aidlAuthToken.mac = keymintAuthToken.mac;
+
+ // Convert timestamp token to KeyMaster verification token
+ TimeStampToken timestampToken = authzTokens.timestampToken;
+ aidlVerificationToken.challenge = timestampToken.challenge;
+ aidlVerificationToken.timestamp.milliSeconds = timestampToken.timestamp.milliSeconds;
+ // Legacy verification tokens were always minted by TEE.
+ aidlVerificationToken.securityLevel = SecurityLevel::TRUSTED_ENVIRONMENT;
+ aidlVerificationToken.mac = timestampToken.mac;
+ } else {
+ if (result.getServiceSpecificError() == 0) {
+ // Here we differentiate the errors occurred during communication
+ // from the service specific errors.
+ LOG(ERROR) << "Error getting tokens from keystore2: " << result.getDescription();
+ return false;
+ } else {
+ // Log the reason for not receiving auth tokens from keystore2.
+ LOG(INFO) << "Auth tokens were not received due to: " << result.getDescription();
+ }
+ }
+ return true;
+ } else {
+ LOG(ERROR) << "Error connecting to IKeystoreAuthorization service";
+ return false;
+ }
+}
+
Status Credential::getEntries(const vector<uint8_t>& requestMessage,
const vector<RequestNamespaceParcel>& requestNamespaces,
const vector<uint8_t>& sessionTranscript,
@@ -334,49 +429,23 @@
// not a guarantee and it's also not required.
//
- vector<uint8_t> authTokenBytes;
- vector<uint8_t> verificationTokenBytes;
- if (!getTokensFromKeystore(selectedChallenge_, data->getSecureUserId(),
- authTokenMaxAgeMillis, authTokenBytes, verificationTokenBytes)) {
- LOG(ERROR) << "Error getting tokens from keystore";
- return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
- "Error getting tokens from keystore");
- }
-
- // It's entirely possible getTokensFromKeystore() succeeded but didn't
- // return any tokens (in which case the returned byte-vectors are
- // empty). For example, this can happen if no auth token is available
- // which satifies e.g. |authTokenMaxAgeMillis|.
- //
- 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);
- aidlAuthToken.authenticatorId = int64_t(authToken.authenticatorId);
- aidlAuthToken.authenticatorType =
- ::android::hardware::keymaster::HardwareAuthenticatorType(
- int32_t(authToken.authenticatorType));
- aidlAuthToken.timestamp.milliSeconds = int64_t(authToken.timestamp);
- aidlAuthToken.mac = authToken.mac;
- }
-
- if (verificationTokenBytes.size() > 0) {
- optional<VerificationToken> token =
- android::hardware::keymaster::V4_0::support::deserializeVerificationToken(
- verificationTokenBytes);
- if (!token) {
- LOG(ERROR) << "Error deserializing verification token";
+ auto keystore2_status = Keystore2Properties::keystore2_enabled();
+ if (keystore2_status.has_value() && keystore2_status.value()) {
+ if (!getTokensFromKeystore2(selectedChallenge_, data->getSecureUserId(),
+ authTokenMaxAgeMillis, aidlAuthToken,
+ aidlVerificationToken)) {
+ LOG(ERROR) << "Error getting tokens from keystore2";
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
- "Error deserializing verification token");
+ "Error getting tokens from keystore2");
}
- aidlVerificationToken.challenge = token->challenge;
- aidlVerificationToken.timestamp.milliSeconds = token->timestamp;
- aidlVerificationToken.securityLevel =
- ::android::hardware::keymaster::SecurityLevel(token->securityLevel);
- aidlVerificationToken.mac = token->mac;
+ } else {
+ if (!getTokensFromKeystore1(selectedChallenge_, data->getSecureUserId(),
+ authTokenMaxAgeMillis, aidlAuthToken,
+ aidlVerificationToken)) {
+ LOG(ERROR) << "Error getting tokens from keystore";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error getting tokens from keystore");
+ }
}
}
diff --git a/identity/main.cpp b/identity/main.cpp
index 8f4968d..b51bd28 100644
--- a/identity/main.cpp
+++ b/identity/main.cpp
@@ -40,7 +40,7 @@
using ::android::security::identity::CredentialStoreFactory;
int main(int argc, char* argv[]) {
- InitLogging(argv, StderrLogger);
+ InitLogging(argv);
CHECK(argc == 2) << "A directory must be specified";
string data_dir = string(argv[1]);
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index d480244..9875d64 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -29,11 +29,12 @@
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.maintenance-rust",
"android.security.remoteprovisioning-rust",
- "android.security.usermanager-rust",
"android.system.keystore2-V1-rust",
"libanyhow",
"libbinder_rs",
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index f30ad83..69ba0b4 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -116,8 +116,8 @@
}
aidl_interface {
- name: "android.security.usermanager",
- srcs: [ "android/security/usermanager/*.aidl" ],
+ name: "android.security.maintenance",
+ srcs: [ "android/security/maintenance/*.aidl" ],
imports: [
"android.system.keystore2",
],
diff --git a/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl b/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl
new file mode 100644
index 0000000..9061998
--- /dev/null
+++ b/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl
@@ -0,0 +1,33 @@
+// 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.authorization;
+
+import android.hardware.security.keymint.HardwareAuthToken;
+import android.hardware.security.secureclock.TimeStampToken;
+
+/**
+ * This parcelable is returned by `IKeystoreAuthorization::getAuthTokensForCredStore`.
+ * @hide
+ */
+parcelable AuthorizationTokens {
+ /**
+ * HardwareAuthToken provided by an authenticator.
+ */
+ HardwareAuthToken authToken;
+ /**
+ * TimeStampToken provided by a SecureClock.
+ */
+ TimeStampToken timestampToken;
+}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
index df64401..6dc172e 100644
--- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
+++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
@@ -16,6 +16,7 @@
import android.hardware.security.keymint.HardwareAuthToken;
import android.security.authorization.LockScreenEvent;
+import android.security.authorization.AuthorizationTokens;
// TODO: mark the interface with @SensitiveData when the annotation is ready (b/176110256).
@@ -45,6 +46,8 @@
* ## Error conditions:
* `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'Unlock' permission.
* `ResponseCode::SYSTEM_ERROR` - if failed to perform lock/unlock operations due to various
+ * `ResponseCode::VALUE_CORRUPTED` - if the super key can not be decrypted.
+ * `ResponseCode::KEY_NOT_FOUND` - if the super key is not found.
*
* @lockScreenEvent - Indicates what happened.
* * LockScreenEvent.UNLOCK if the screen was unlocked.
@@ -56,4 +59,37 @@
*/
void onLockScreenEvent(in LockScreenEvent lockScreenEvent, in int userId,
in @nullable byte[] password);
+
+ /**
+ * Allows Credstore to retrieve a HardwareAuthToken and a TimestampToken.
+ * Identity Credential Trusted App can run either in the TEE or in other secure Hardware.
+ * So, credstore always need to retrieve a TimestampToken along with a HardwareAuthToken.
+ *
+ * The passed in |challenge| parameter must always be non-zero.
+ *
+ * The returned TimestampToken will always have its |challenge| field set to
+ * the |challenge| parameter.
+ *
+ * This method looks through auth-tokens cached by keystore which match
+ * the passed-in |secureUserId|.
+ * The most recent matching auth token which has a |challenge| field which matches
+ * the passed-in |challenge| parameter is returned.
+ * In this case the |authTokenMaxAgeMillis| parameter is not used.
+ *
+ * Otherwise, the most recent matching auth token which is younger
+ * than |authTokenMaxAgeMillis| is returned.
+ *
+ * This method is called by credstore (and only credstore).
+ *
+ * The caller requires 'get_auth_token' permission.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'get_auth_token'
+ * permission.
+ * `ResponseCode::SYSTEM_ERROR` - if failed to obtain an authtoken from the database.
+ * `ResponseCode::NO_AUTH_TOKEN_FOUND` - a matching auth token is not found.
+ * `ResponseCode::INVALID_ARGUMENT` - if the passed-in |challenge| parameter is zero.
+ */
+ AuthorizationTokens getAuthTokensForCredStore(in long challenge, in long secureUserId,
+ in long authTokenMaxAgeMillis);
}
diff --git a/keystore2/aidl/android/security/authorization/ResponseCode.aidl b/keystore2/aidl/android/security/authorization/ResponseCode.aidl
new file mode 100644
index 0000000..94f1120
--- /dev/null
+++ b/keystore2/aidl/android/security/authorization/ResponseCode.aidl
@@ -0,0 +1,56 @@
+// 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.authorization;
+
+/**
+ * Used as exception codes by IKeystoreAuthorization.
+ */
+@Backing(type="int")
+enum ResponseCode {
+ /**
+ * A matching auth token is not found.
+ */
+ NO_AUTH_TOKEN_FOUND = 1,
+ /**
+ * The matching auth token is expired.
+ */
+ AUTH_TOKEN_EXPIRED = 2,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Any unexpected Error such as IO or communication errors.
+ */
+ SYSTEM_ERROR = 4,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that the caller does not have the permissions for the attempted request.
+ */
+ PERMISSION_DENIED = 6,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that the requested key does not exist.
+ */
+ KEY_NOT_FOUND = 7,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that a value being processed is corrupted.
+ */
+ VALUE_CORRUPTED = 8,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that an invalid argument was passed to an API call.
+ */
+ INVALID_ARGUMENT = 20,
+
+ }
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
similarity index 81%
rename from keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl
rename to keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
index 83edb1a..3115e92 100644
--- a/keystore2/aidl/android/security/usermanager/IKeystoreUserManager.aidl
+++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
@@ -12,18 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package android.security.usermanager;
+package android.security.maintenance;
import android.system.keystore2.Domain;
+import android.security.maintenance.UserState;
// 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
+ * IKeystoreMaintenance interface exposes the methods for adding/removing users and changing the
* user's password.
* @hide
*/
-interface IKeystoreUserManager {
+interface IKeystoreMaintenance {
/**
* Allows LockSettingsService to inform keystore about adding a new user.
@@ -75,4 +76,17 @@
* @hide
*/
void clearNamespace(Domain domain, long nspace);
+
+ /**
+ * Allows querying user state, given user id.
+ * Callers require 'GetState' permission.
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'GetState'
+ * permission.
+ * `ResponseCode::SYSTEM_ERROR` - if an error occurred when querying the user state.
+ *
+ * @param userId - Android user id
+ * @hide
+ */
+ UserState getState(in int userId);
}
diff --git a/keystore2/aidl/android/security/maintenance/UserState.aidl b/keystore2/aidl/android/security/maintenance/UserState.aidl
new file mode 100644
index 0000000..b6fe278
--- /dev/null
+++ b/keystore2/aidl/android/security/maintenance/UserState.aidl
@@ -0,0 +1,22 @@
+// 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.maintenance;
+
+@Backing(type="int")
+enum UserState {
+ UNINITIALIZED = 0,
+ LSKF_UNLOCKED = 1,
+ LSKF_LOCKED = 2,
+}
\ No newline at end of file
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index 2b5091d..cc707e7 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -455,7 +455,6 @@
check_keystore_perm!(add_auth);
check_keystore_perm!(clear_ns);
- check_keystore_perm!(get_state);
check_keystore_perm!(lock);
check_keystore_perm!(reset);
check_keystore_perm!(unlock);
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 02b19c4..75c5add 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -15,7 +15,6 @@
//! This module implements IKeystoreAuthorization AIDL interface.
use crate::error::Error as KeystoreError;
-use crate::error::map_or_log_err;
use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
use crate::permission::KeystorePerm;
use crate::super_key::UserState;
@@ -23,14 +22,87 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
};
-use android_security_authorization::binder::{Interface, Result as BinderResult, Strong};
-use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::{
- BnKeystoreAuthorization, IKeystoreAuthorization,
+use android_security_authorization::binder::{ExceptionCode, Interface, Result as BinderResult,
+ Strong, Status as BinderStatus};
+use android_security_authorization::aidl::android::security::authorization::{
+ IKeystoreAuthorization::BnKeystoreAuthorization, IKeystoreAuthorization::IKeystoreAuthorization,
+ LockScreenEvent::LockScreenEvent, AuthorizationTokens::AuthorizationTokens,
+ ResponseCode::ResponseCode,
};
-use android_security_authorization:: aidl::android::security::authorization::LockScreenEvent::LockScreenEvent;
-use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ ResponseCode::ResponseCode as KsResponseCode };
use anyhow::{Context, Result};
use binder::IBinder;
+use keystore2_selinux as selinux;
+
+/// This is the Authorization error type, it wraps binder exceptions and the
+/// Authorization ResponseCode
+#[derive(Debug, thiserror::Error, PartialEq)]
+pub enum Error {
+ /// Wraps an IKeystoreAuthorization response code as defined by
+ /// android.security.authorization AIDL interface specification.
+ #[error("Error::Rc({0:?})")]
+ Rc(ResponseCode),
+ /// Wraps a Binder exception code other than a service specific exception.
+ #[error("Binder exception code {0:?}, {1:?}")]
+ Binder(ExceptionCode, i32),
+}
+
+/// This function should be used by authorization service calls to translate error conditions
+/// into service specific exceptions.
+///
+/// All error conditions get logged by this function.
+///
+/// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`.
+/// Certain response codes may be returned from keystore/ResponseCode.aidl by the keystore2 modules,
+/// which are then converted to the corresponding response codes of android.security.authorization
+/// AIDL interface specification.
+///
+/// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`.
+///
+/// All non `Error` error conditions get mapped onto ResponseCode::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).
+pub 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();
+ if let Some(KeystoreError::Rc(ks_rcode)) = root_cause.downcast_ref::<KeystoreError>() {
+ let rc = match *ks_rcode {
+ // Although currently keystore2/ResponseCode.aidl and
+ // authorization/ResponseCode.aidl share the same integer values for the
+ // common response codes, this may deviate in the future, hence the
+ // conversion here.
+ KsResponseCode::SYSTEM_ERROR => ResponseCode::SYSTEM_ERROR.0,
+ KsResponseCode::KEY_NOT_FOUND => ResponseCode::KEY_NOT_FOUND.0,
+ KsResponseCode::VALUE_CORRUPTED => ResponseCode::VALUE_CORRUPTED.0,
+ KsResponseCode::INVALID_ARGUMENT => ResponseCode::INVALID_ARGUMENT.0,
+ // If the code paths of IKeystoreAuthorization aidl's methods happen to return
+ // other error codes from KsResponseCode in the future, they should be converted
+ // as well.
+ _ => ResponseCode::SYSTEM_ERROR.0,
+ };
+ return Err(BinderStatus::new_service_specific_error(rc, None));
+ }
+ let rc = match root_cause.downcast_ref::<Error>() {
+ Some(Error::Rc(rcode)) => rcode.0,
+ Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0,
+ None => match root_cause.downcast_ref::<selinux::Error>() {
+ Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
+ _ => ResponseCode::SYSTEM_ERROR.0,
+ },
+ };
+ Err(BinderStatus::new_service_specific_error(rc, None))
+ },
+ handle_ok,
+ )
+}
/// This struct is defined to implement the aforementioned AIDL interface.
/// As of now, it is an empty struct.
@@ -99,11 +171,33 @@
}
_ => {
// Any other combination is not supported.
- Err(KeystoreError::Rc(ResponseCode::INVALID_ARGUMENT))
+ Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
.context("In on_lock_screen_event: Unknown event.")
}
}
}
+
+ fn get_auth_tokens_for_credstore(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> Result<AuthorizationTokens> {
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::get_auth_token())
+ .context("In get_auth_tokens_for_credstore.")?;
+
+ // if the challenge is zero, return error
+ if challenge == 0 {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("In get_auth_tokens_for_credstore. Challenge can not be zero.");
+ }
+ // Obtain the auth token and the timestamp token from the enforcement module.
+ let (auth_token, ts_token) =
+ ENFORCEMENTS.get_auth_tokens(challenge, secure_user_id, auth_token_max_age_millis)?;
+ Ok(AuthorizationTokens { authToken: auth_token, timestampToken: ts_token })
+ }
}
impl Interface for AuthorizationManager {}
@@ -121,4 +215,20 @@
) -> BinderResult<()> {
map_or_log_err(self.on_lock_screen_event(lock_screen_event, user_id, password), Ok)
}
+
+ fn getAuthTokensForCredStore(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> binder::public_api::Result<AuthorizationTokens> {
+ map_or_log_err(
+ self.get_auth_tokens_for_credstore(
+ challenge,
+ secure_user_id,
+ auth_token_max_age_millis,
+ ),
+ Ok,
+ )
+ }
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index a9e4c35..3bcc9a4 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -738,6 +738,11 @@
self.0
}
+ /// Returns the value of MonotonicRawTime in milli seconds as i64
+ pub fn milli_seconds(&self) -> i64 {
+ self.0 * 1000
+ }
+
/// Like i64::checked_sub.
pub fn checked_sub(&self, other: &Self) -> Option<Self> {
self.0.checked_sub(other.0).map(Self)
@@ -790,6 +795,11 @@
pub fn time_received(&self) -> MonotonicRawTime {
self.time_received
}
+
+ /// Returns the challenge value of the auth token.
+ pub fn challenge(&self) -> i64 {
+ self.auth_token.challenge
+ }
}
/// Shared in-memory databases get destroyed as soon as the last connection to them gets closed.
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index aa852f4..06648f1 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -14,6 +14,7 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
+use crate::authorization::Error as AuthzError;
use crate::database::{AuthTokenEntry, MonotonicRawTime};
use crate::error::{map_binder_status, Error, ErrorCode};
use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS};
@@ -26,6 +27,7 @@
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
};
+use android_security_authorization::aidl::android::security::authorization::ResponseCode::ResponseCode as AuthzResponseCode;
use android_system_keystore2::aidl::android::system::keystore2::{
IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
OperationChallenge::OperationChallenge,
@@ -218,7 +220,7 @@
if let Err(e) = sender.send(get_timestamp_token(challenge)) {
log::info!(
concat!(
- "In timestamp_token_request: Operation hung up ",
+ "In timestamp_token_request: Receiver hung up ",
"before timestamp token could be delivered. {:?}"
),
e
@@ -767,6 +769,66 @@
auth_bound && !skip_lskf_binding
}
+ /// Finds a matching auth token along with a timestamp token.
+ /// This method looks through auth-tokens cached by keystore which satisfy the given
+ /// authentication information (i.e. |secureUserId|).
+ /// The most recent matching auth token which has a |challenge| field which matches
+ /// the passed-in |challenge| parameter is returned.
+ /// In this case the |authTokenMaxAgeMillis| parameter is not used.
+ ///
+ /// Otherwise, the most recent matching auth token which is younger than |authTokenMaxAgeMillis|
+ /// is returned.
+ pub fn get_auth_tokens(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> Result<(HardwareAuthToken, TimeStampToken)> {
+ let auth_type = HardwareAuthenticatorType::ANY;
+ let sids: Vec<i64> = vec![secure_user_id];
+ // Filter the matching auth tokens by challenge
+ let result = Self::find_auth_token(|hat: &AuthTokenEntry| {
+ (challenge == hat.challenge()) && hat.satisfies(&sids, auth_type)
+ })
+ .context(
+ "In get_auth_tokens: Failed to get a matching auth token filtered by challenge.",
+ )?;
+
+ let auth_token = if let Some((auth_token_entry, _)) = result {
+ auth_token_entry.take_auth_token()
+ } else {
+ // Filter the matching auth tokens by age.
+ if auth_token_max_age_millis != 0 {
+ let now_in_millis = MonotonicRawTime::now().milli_seconds();
+ let result = Self::find_auth_token(|auth_token_entry: &AuthTokenEntry| {
+ let token_valid = now_in_millis
+ .checked_sub(auth_token_entry.time_received().milli_seconds())
+ .map_or(false, |token_age_in_millis| {
+ token_age_in_millis > auth_token_max_age_millis
+ });
+ token_valid && auth_token_entry.satisfies(&sids, auth_type)
+ })
+ .context(
+ "In get_auth_tokens: Failed to get a matching auth token filtered by age.",
+ )?;
+
+ if let Some((auth_token_entry, _)) = result {
+ auth_token_entry.take_auth_token()
+ } else {
+ return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
+ .context("In get_auth_tokens: No auth token found.");
+ }
+ } else {
+ return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
+ .context("In get_auth_tokens: Passed-in auth token max age is zero.");
+ }
+ };
+ // Wait and obtain the timestamp token from secure clock service.
+ let tst = get_timestamp_token(challenge)
+ .context("In get_auth_tokens. Error in getting timestamp token.")?;
+ Ok((auth_token, tst))
+ }
+
/// 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<()> {
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 7739f5e..51c78b1 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -19,7 +19,7 @@
use keystore2::globals::ENFORCEMENTS;
use keystore2::remote_provisioning::RemoteProvisioningService;
use keystore2::service::KeystoreService;
-use keystore2::user_manager::UserManager;
+use keystore2::user_manager::Maintenance;
use log::{error, info};
use std::{panic, path::Path, sync::mpsc::channel};
use vpnprofilestore::VpnProfileStore;
@@ -28,7 +28,7 @@
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 USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance";
static VPNPROFILESTORE_SERVICE_NAME: &str = "android.security.vpnprofilestore";
/// Keystore 2.0 takes one argument which is a path indicating its designated working directory.
@@ -100,10 +100,10 @@
panic!("Failed to register service {} because of {:?}.", AUTHORIZATION_SERVICE_NAME, e);
});
- let usermanager_service = UserManager::new_native_binder().unwrap_or_else(|e| {
+ let maintenance_service = Maintenance::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(
+ binder::add_service(USER_MANAGER_SERVICE_NAME, maintenance_service.as_binder()).unwrap_or_else(
|e| {
panic!("Failed to register service {} because of {:?}.", USER_MANAGER_SERVICE_NAME, e);
},
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 3439d2f..773b4b9 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -216,6 +216,8 @@
*/
bool isNewAndKeystoreEnforceable(const KMV1::KeyParameter& param) {
switch (param.tag) {
+ case KMV1::Tag::MAX_BOOT_LEVEL:
+ return true;
case KMV1::Tag::USAGE_COUNT_LIMIT:
return true;
default:
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 576ac3f..7f63834 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -291,7 +291,7 @@
AddAuth = 1, selinux name: add_auth;
/// Checked when an app is uninstalled or wiped.
ClearNs = 2, selinux name: clear_ns;
- /// Checked when Keystore 2.0 gets locked.
+ /// Checked when the user state is queried from Keystore 2.0.
GetState = 4, selinux name: get_state;
/// Checked when Keystore 2.0 is asked to list a namespace that the caller
/// does not have the get_info permission for.
@@ -308,6 +308,8 @@
ChangePassword = 0x100, selinux name: change_password;
/// Checked when a UID is cleared.
ClearUID = 0x200, selinux name: clear_uid;
+ /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens.
+ GetAuthToken = 0x400, selinux name: get_auth_token;
}
);
@@ -673,7 +675,7 @@
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()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state()));
+ assert!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state()).is_ok());
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::list()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::lock()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::reset()));
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index aee3b82..6560d4d 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -38,7 +38,10 @@
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
use crate::remote_provisioning::RemProvState;
use crate::super_key::{KeyBlob, SuperKeyManager};
-use crate::utils::{check_key_permission, uid_to_android_user, Asp};
+use crate::utils::{
+ check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
+ uid_to_android_user, Asp,
+};
use crate::{
database::{
BlobMetaData, BlobMetaEntry, DateTime, KeyEntry, KeyEntryLoadBits, KeyMetaData,
@@ -359,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) {
@@ -408,17 +420,20 @@
// generate_key requires the rebind permission.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In generate_key.")?;
- let (attest_key, cert_chain) = 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 (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.")?;
diff --git a/keystore2/src/user_manager.rs b/keystore2/src/user_manager.rs
index 3c393c5..c17fa72 100644
--- a/keystore2/src/user_manager.rs
+++ b/keystore2/src/user_manager.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module implements IKeystoreUserManager AIDL interface.
+//! This module implements IKeystoreMaintenance AIDL interface.
use crate::error::map_or_log_err;
use crate::error::Error as KeystoreError;
@@ -20,10 +20,11 @@
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_maintenance::aidl::android::security::maintenance::{
+ IKeystoreMaintenance::{BnKeystoreMaintenance, IKeystoreMaintenance},
+ UserState::UserState as AidlUserState,
};
-use android_security_usermanager::binder::{Interface, Result as BinderResult};
+use android_security_maintenance::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};
@@ -31,12 +32,12 @@
/// This struct is defined to implement the aforementioned AIDL interface.
/// As of now, it is an empty struct.
-pub struct UserManager;
+pub struct Maintenance;
-impl UserManager {
+impl Maintenance {
/// Create a new instance of Keystore User Manager service.
- pub fn new_native_binder() -> Result<Strong<dyn IKeystoreUserManager>> {
- let result = BnKeystoreUserManager::new_binder(Self);
+ pub fn new_native_binder() -> Result<Strong<dyn IKeystoreMaintenance>> {
+ let result = BnKeystoreMaintenance::new_binder(Self);
result.as_binder().set_requesting_sid(true);
Ok(result)
}
@@ -97,11 +98,28 @@
DB.with(|db| db.borrow_mut().unbind_keys_for_namespace(domain, nspace))
.context("In clear_namespace: Trying to delete keys from db.")
}
+
+ fn get_state(user_id: i32) -> Result<AidlUserState> {
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::get_state()).context("In get_state.")?;
+ let state = DB
+ .with(|db| {
+ UserState::get(&mut db.borrow_mut(), &LEGACY_MIGRATOR, &SUPER_KEY, user_id as u32)
+ })
+ .context("In get_state. Trying to get UserState.")?;
+
+ match state {
+ UserState::Uninitialized => Ok(AidlUserState::UNINITIALIZED),
+ UserState::LskfUnlocked(_) => Ok(AidlUserState::LSKF_UNLOCKED),
+ UserState::LskfLocked => Ok(AidlUserState::LSKF_LOCKED),
+ }
+ }
}
-impl Interface for UserManager {}
+impl Interface for Maintenance {}
-impl IKeystoreUserManager for UserManager {
+impl IKeystoreMaintenance for Maintenance {
fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> {
map_or_log_err(Self::on_user_password_changed(user_id, password), Ok)
}
@@ -117,4 +135,8 @@
fn clearNamespace(&self, domain: Domain, nspace: i64) -> BinderResult<()> {
map_or_log_err(Self::clear_namespace(domain, nspace), Ok)
}
+
+ fn getState(&self, user_id: i32) -> binder::public_api::Result<AidlUserState> {
+ map_or_log_err(Self::get_state(user_id), 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/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index 5db19b7..1c3706d 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -26,7 +26,6 @@
tidy_errors = [
"cert-err34-c",
"google-default-arguments",
- "google-explicit-constructor",
"google-runtime-int",
"google-runtime-member-string-references",
"misc-move-const-arg",
@@ -87,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",
@@ -106,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..b0b75a6 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";
}
@@ -112,11 +147,14 @@
x509->signature->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
x509->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT;
- auto f = fopen(path.c_str(), "wb");
- // TODO error checking
+ auto f = fopen(path.c_str(), "wbe");
+ if (f == nullptr) {
+ return Error() << "Failed to open " << path;
+ }
i2d_X509_fp(f, x509.get());
fclose(f);
+ EVP_PKEY_free(public_key);
return {};
}
@@ -142,17 +180,33 @@
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");
+ auto f = fopen(path.c_str(), "re");
+ if (f == nullptr) {
+ return Error() << "Failed to open " << path;
+ }
if (!d2i_X509_fp(f, &cert)) {
+ fclose(f);
return Error() << "Unable to decode x509 cert at " << path;
}
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/KeymasterSigningKey.cpp b/ondevice-signing/KeymasterSigningKey.cpp
index 2b748e4..dc3ef8a 100644
--- a/ondevice-signing/KeymasterSigningKey.cpp
+++ b/ondevice-signing/KeymasterSigningKey.cpp
@@ -33,30 +33,36 @@
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);
+ return signingKey;
}
-Result<KeymasterSigningKey> KeymasterSigningKey::createNewKey() {
- KeymasterSigningKey signingKey;
+Result<void> KeymasterSigningKey::saveKeyblob(const std::string& path) const {
+ int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
- auto status = signingKey.createSigningKey();
-
- if (!status.ok()) {
- return status.error();
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, 0600)));
+ if (fd == -1) {
+ return ErrnoError() << "Error creating key blob file " << path;
}
- return std::move(signingKey);
+ if (!android::base::WriteFully(fd, mVerifiedKeyBlob.data(), mVerifiedKeyBlob.size())) {
+ return ErrnoError() << "Error writing key blob file " << path;
+ } else {
+ return {};
+ }
}
Result<void> KeymasterSigningKey::createSigningKey() {
@@ -78,41 +84,45 @@
return {};
}
-Result<void> KeymasterSigningKey::saveKeyblob(const std::string& path) const {
- int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
+Result<std::unique_ptr<KeymasterSigningKey>> KeymasterSigningKey::createAndPersistNewKey() {
+ auto signingKey = std::make_unique<KeymasterSigningKey>();
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, 0600)));
- if (fd == -1) {
- return ErrnoError() << "Error creating key blob file " << path;
+ auto status = signingKey->createSigningKey();
+
+ if (!status.ok()) {
+ return status.error();
}
- if (!android::base::WriteFully(fd, mVerifiedKeyBlob.data(), mVerifiedKeyBlob.size())) {
- return ErrnoError() << "Error writing key blob file " << path;
- } else {
- return {};
+ 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 publicKeyX509 = mKeymaster->extractPublicKey(mVerifiedKeyBlob);
- if (!publicKeyX509.ok()) {
- return publicKeyX509.error();
- }
- return extractPublicKeyFromX509(publicKeyX509.value());
-}
-
-Result<void> KeymasterSigningKey::createX509Cert(const std::string& outPath) 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) {
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..de7033f
--- /dev/null
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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;
+
+// Keystore boot level that the odsign key uses
+static const int kOdsignBootLevel = 30;
+
+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);
+
+ KeyParameter boot_level;
+ boot_level.tag = Tag::MAX_BOOT_LEVEL;
+ boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(kOdsignBootLevel);
+ params.push_back(boot_level);
+
+ 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()) {
+ status = mService->getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT, &mSecurityLevel);
+ if (!status.isOk()) {
+ 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 b4a6a54..ff7de7e 100644
--- a/ondevice-signing/VerityUtils.cpp
+++ b/ondevice-signing/VerityUtils.cpp
@@ -15,12 +15,15 @@
*/
#include <filesystem>
+#include <map>
+#include <span>
#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 +31,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 +57,21 @@
__u8 digest[];
};
+static std::string toHex(std::span<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,19 +89,36 @@
return std::vector<uint8_t>(&digest->digest[0], &digest->digest[32]);
}
-static Result<std::vector<uint8_t>> signDigest(const KeymasterSigningKey& key,
+namespace {
+template <typename T> struct DeleteAsPODArray {
+ void operator()(T* x) {
+ if (x) {
+ x->~T();
+ delete[](uint8_t*) x;
+ }
+ }
+};
+} // namespace
+
+template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>;
+
+template <typename T>
+static trailing_unique_ptr<T> makeUniqueWithTrailingData(size_t trailing_data_size) {
+ uint8_t* memory = new uint8_t[sizeof(T*) + trailing_data_size];
+ T* ptr = new (memory) T;
+ return trailing_unique_ptr<T>{ptr};
+}
+
+static Result<std::vector<uint8_t>> signDigest(const SigningKey& key,
const std::vector<uint8_t>& digest) {
- fsverity_signed_digest* d;
- size_t signed_digest_size = sizeof(*d) + digest.size();
- std::unique_ptr<uint8_t[]> digest_buffer{new uint8_t[signed_digest_size]};
- d = (fsverity_signed_digest*)digest_buffer.get();
+ auto d = makeUniqueWithTrailingData<fsverity_signed_digest>(digest.size());
memcpy(d->magic, "FSVerity", 8);
d->digest_algorithm = cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
d->digest_size = cpu_to_le16(digest.size());
memcpy(d->digest, digest.data(), digest.size());
- auto signed_digest = key.sign(std::string((char*)d, signed_digest_size));
+ auto signed_digest = key.sign(std::string((char*)d.get(), sizeof(*d) + digest.size()));
if (!signed_digest.ok()) {
return signed_digest.error();
}
@@ -94,7 +126,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();
@@ -121,10 +153,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);
@@ -137,14 +172,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)));
@@ -156,11 +195,21 @@
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);
+ auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+ ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
+ if (ret < 0) {
+ return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY for " << path;
+ }
+ return toHex({&d->digest[0], &d->digest[d->digest_size]});
}
-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);
@@ -173,12 +222,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 3baba68..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,69 +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/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"};
-
- // NOLINTNEXTLINE(android-cloexec-open): Deliberately not O_CLOEXEC
- 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> verifyExistingCert(const KeymasterSigningKey& key) {
+Result<void> verifyExistingCert(const SigningKey& key) {
if (access(kSigningKeyCert.c_str(), F_OK) < 0) {
return ErrnoError() << "Key certificate not found: " << kSigningKeyCert;
}
@@ -114,19 +78,18 @@
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) {
@@ -143,73 +106,251 @@
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;
- }
-
- auto existing_cert = verifyExistingCert(key.value());
- if (!existing_cert.ok()) {
- LOG(WARNING) << existing_cert.error().message();
-
- // 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
+ // 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();
+ }
+
+ bool supportsFsVerity = access(kFsVerityProcPath, F_OK) == 0;
+ if (!supportsFsVerity) {
+ LOG(INFO) << "Device doesn't support fsverity. Falling back to full verification.";
+ }
+
+ 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()) {
+ LOG(ERROR) << "Failed to add certificate to fs-verity keyring: "
+ << 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;
- }
- auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
- if (!cert_add_result.ok()) {
- LOG(ERROR) << "Failed to add certificate to fs-verity keyring: "
- << cert_add_result.error().message();
- return -1;
+ 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;
+}