Identity Credential: Add method to accept verification token.
This is to facilitate HAL implementations using a TA existing in a
different environment than where auth tokens are minted. This method
will be used by credstore in a companion CL.
This modifies version 2 of the Identity Credential API (which was
never been released) to add a new method and creates version 2 of the
Keymaster types-only AIDL API to include the new VerificationToken
parcelable and SecurityLevel enum.
Bug: 156076333
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Change-Id: I7d05413a9ec70225ce419079f3cc9daf026cf744
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/.hash b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
index 1b2c1b6..036ce84 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/2/.hash
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
@@ -1 +1 @@
-3b0b10b618dbc4bf283aa2bf78833ad3de0a5928
+194e04be642728623d65ec8321a3764fdea52ae0
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
index e8e93f8..88104d9 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
@@ -28,4 +28,5 @@
void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
+ void setVerificationToken(in android.hardware.keymaster.VerificationToken verificationToken);
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
index e8e93f8..88104d9 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
@@ -28,4 +28,5 @@
void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
+ void setVerificationToken(in android.hardware.keymaster.VerificationToken verificationToken);
}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index 7cd25e6..d7f47e8 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -20,6 +20,7 @@
import android.hardware.identity.RequestNamespace;
import android.hardware.identity.SecureAccessControlProfile;
import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.VerificationToken;
@VintfStability
interface IIdentityCredential {
@@ -71,10 +72,11 @@
/**
* Creates a challenge value to be used for proving successful user authentication. This
- * is included in the authToken passed to the startRetrieval() method.
+ * is included in the authToken passed to the startRetrieval() method and the
+ * verificationToken passed to the setVerificationToken() method.
*
* This method may only be called once per instance. If called more than once, STATUS_FAILED
- * will be returned.
+ * will be returned. If user authentication is not needed, this method may not be called.
*
* @return challenge, a non-zero number.
*/
@@ -83,7 +85,8 @@
/**
* Start an entry retrieval process.
*
- * The setRequestedNamespaces() method will be called before this method.
+ * The setRequestedNamespaces() and setVerificationToken() methods will be called before
+ * this method is called.
*
* This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
* createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
@@ -96,7 +99,19 @@
* must be identical for each startRetrieval() invocation. If this is not the case, this call
* fails with the STATUS_SESSION_TRANSCRIPT_MISMATCH error.
*
- * If the provided authToken is not valid this method fails with STATUS_INVALID_AUTH_TOKEN.
+ * If either authToken or verificationToken (as passed with setVerificationToken())
+ * is not valid this method fails with STATUS_INVALID_AUTH_TOKEN. Note that valid tokens
+ * are only passed if they are actually needed and available (this can be detected by
+ * the timestamp being set to zero). For example, if no data items with access control
+ * profiles using user authentication are requested, the tokens are not filled in.
+ * It's also possible that no usable auth token is actually available (it could be the user
+ * never unlocked the device within the timeouts in the access control profiles) and
+ * in this case the tokens aren't filled in either.
+ *
+ * For test credentials (identified by the testCredential boolean in the CredentialData
+ * CBOR created at provisioning time), the |mac| field in both the authToken and
+ * verificationToken should not be checked against the shared HMAC key (see IKeyMasterDevice
+ * for details). This is to enable VTS tests to check for correct behavior.
*
* Each of the provided accessControlProfiles is checked in this call. If they are not
* all valid, the call fails with STATUS_INVALID_DATA.
@@ -179,7 +194,8 @@
*
* @param authToken
* The authentication token that proves the user was authenticated, as required
- * by one or more of the provided accessControlProfiles. See above.
+ * by one or more of the provided accessControlProfiles. This token is only valid
+ * if the timestamp field is non-zero. See above.
*
* @param itemsRequest
* If non-empty, contains request data that is signed by the reader. See above.
@@ -358,4 +374,13 @@
* @param requestNamespaces Namespaces and data items which will be requested.
*/
void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
+
+ /**
+ * Sets the VerificationToken. This method must be called before startRetrieval() is
+ * called. This token uses the same challenge as returned by createAuthChallenge().
+ *
+ * @param verificationToken
+ * The verification token. This token is only valid if the timestamp field is non-zero.
+ */
+ void setVerificationToken(in VerificationToken verificationToken);
}
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
index ff4107a..381eb84 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -198,15 +198,8 @@
return false;
}
-Timestamp clockGetTime() {
- struct timespec time;
- clock_gettime(CLOCK_MONOTONIC, &time);
- Timestamp ts;
- ts.milliSeconds = time.tv_sec * 1000 + time.tv_nsec / 1000000;
- return ts;
-}
-
bool checkUserAuthentication(const SecureAccessControlProfile& profile,
+ const VerificationToken& verificationToken,
const HardwareAuthToken& authToken, uint64_t authChallenge) {
if (profile.secureUserId != authToken.userId) {
LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
@@ -214,6 +207,15 @@
return false;
}
+ if (verificationToken.timestamp.milliSeconds == 0) {
+ LOG(ERROR) << "VerificationToken is not set";
+ return false;
+ }
+ if (authToken.timestamp.milliSeconds == 0) {
+ LOG(ERROR) << "AuthToken is not set";
+ return false;
+ }
+
if (profile.timeoutMillis == 0) {
if (authToken.challenge == 0) {
LOG(ERROR) << "No challenge in authToken";
@@ -227,19 +229,11 @@
return true;
}
- // Note that the Epoch for timestamps in HardwareAuthToken is at the
- // discretion of the vendor:
+ // Timeout-based user auth follows. The verification token conveys what the
+ // time is right now in the environment which generated the auth token. This
+ // is what makes it possible to do timeout-based checks.
//
- // "[...] since some starting point (generally the most recent device
- // boot) which all of the applications within one secure environment
- // must agree upon."
- //
- // Therefore, if this software implementation is used on a device which isn't
- // the emulator then the assumption that the epoch is the same as used in
- // clockGetTime above will not hold. This is OK as this software
- // implementation should never be used on a real device.
- //
- Timestamp now = clockGetTime();
+ const Timestamp now = verificationToken.timestamp;
if (authToken.timestamp.milliSeconds > now.milliSeconds) {
LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
<< ") is in the future (now: " << now.milliSeconds << ")";
@@ -261,6 +255,12 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus IdentityCredential::setVerificationToken(
+ const VerificationToken& verificationToken) {
+ verificationToken_ = verificationToken;
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus IdentityCredential::startRetrieval(
const vector<SecureAccessControlProfile>& accessControlProfiles,
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -483,7 +483,8 @@
}
int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
if (profile.userAuthenticationRequired) {
- if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
+ if (!haveAuthToken ||
+ !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
}
} else if (profile.readerCertificate.encodedCertificate.size() > 0) {
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/IdentityCredential.h
index a8bad88..a82531d 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/IdentityCredential.h
@@ -19,6 +19,7 @@
#include <aidl/android/hardware/identity/BnIdentityCredential.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <map>
@@ -31,6 +32,7 @@
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::HardwareAuthToken;
+using ::aidl::android::hardware::keymaster::VerificationToken;
using ::std::map;
using ::std::set;
using ::std::string;
@@ -55,6 +57,7 @@
ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
ndk::ScopedAStatus setRequestedNamespaces(
const vector<RequestNamespace>& requestNamespaces) override;
+ ndk::ScopedAStatus setVerificationToken(const VerificationToken& verificationToken) override;
ndk::ScopedAStatus startRetrieval(
const vector<SecureAccessControlProfile>& accessControlProfiles,
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -93,6 +96,9 @@
// Set by setRequestedNamespaces()
vector<RequestNamespace> requestNamespaces_;
+ // Set by setVerificationToken().
+ VerificationToken verificationToken_;
+
// Set at startRetrieval() time.
map<int32_t, int> profileIdToAccessCheckResult_;
vector<uint8_t> signingKeyBlob_;
diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp
index f05c615..bf95df5 100644
--- a/identity/aidl/default/service.cpp
+++ b/identity/aidl/default/service.cpp
@@ -22,9 +22,14 @@
#include "IdentityCredentialStore.h"
+using ::android::base::InitLogging;
+using ::android::base::StderrLogger;
+
using aidl::android::hardware::identity::IdentityCredentialStore;
-int main() {
+int main(int /*argc*/, char* argv[]) {
+ InitLogging(argv, StderrLogger);
+
ABinderProcess_setThreadPoolMaxThreadCount(0);
std::shared_ptr<IdentityCredentialStore> store =
ndk::SharedRefBase::make<IdentityCredentialStore>();
diff --git a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
index 807feab..17145b4 100644
--- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -43,6 +43,7 @@
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
class IdentityAidl : public testing::TestWithParam<std::string> {
public:
@@ -82,7 +83,20 @@
// Profile 1 (no authentication)
{1, {}, false, 0}};
+ // It doesn't matter since no user auth is needed in this particular test,
+ // but for good measure, clear out the tokens we pass to the HAL.
HardwareAuthToken authToken;
+ VerificationToken verificationToken;
+ authToken.challenge = 0;
+ authToken.userId = 0;
+ authToken.authenticatorId = 0;
+ authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
+ authToken.timestamp.milliSeconds = 0;
+ authToken.mac.clear();
+ verificationToken.challenge = 0;
+ verificationToken.timestamp.milliSeconds = 0;
+ verificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE;
+ verificationToken.mac.clear();
// Here's the actual test data:
const vector<test_utils::TestEntryData> testEntries = {
@@ -274,7 +288,10 @@
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
- ASSERT_TRUE(credential->setRequestedNamespaces(requestedNamespaces).isOk());
+ // OK to fail, not available in v1 HAL
+ credential->setRequestedNamespaces(requestedNamespaces).isOk();
+ // OK to fail, not available in v1 HAL
+ credential->setVerificationToken(verificationToken);
ASSERT_TRUE(credential
->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes,
signingKeyBlob, sessionTranscriptBytes,