identity: Add multi-document presentation support.
This new IPresentationSession interface enables an application to do a
multi-document presentation, something which isn't possible with the
existing API. As a practical example of this consider presenting both
your Mobile Driving License and your Vaccination Certificate in a single
transaction.
Bug: 197965513
Test: New CTS tests and new screen in CtsVerifier
Change-Id: I11712dca35df7f1224debf454731bc17ea9bfb37
diff --git a/identity/aidl/default/common/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp
index 95557b5..7678ecb 100644
--- a/identity/aidl/default/common/IdentityCredential.cpp
+++ b/identity/aidl/default/common/IdentityCredential.cpp
@@ -72,14 +72,38 @@
testCredential_ = testCredentialItem->value();
encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
- if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys_)) {
- LOG(ERROR) << "hwProxy->initialize failed";
- return false;
+
+ // If in a session, delay the initialization of the proxy.
+ //
+ if (!session_) {
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ LOG(ERROR) << "Error initializing hw proxy";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
}
return IIdentityCredentialStore::STATUS_OK;
}
+ndk::ScopedAStatus IdentityCredential::ensureHwProxy() {
+ if (hwProxy_) {
+ return ndk::ScopedAStatus::ok();
+ }
+ hwProxy_ = hwProxyFactory_->createPresentationProxy();
+ if (!hwProxy_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating hw proxy"));
+ }
+ uint64_t sessionId = session_ ? session_->getSessionId() : EIC_PRESENTATION_ID_UNSET;
+ if (!hwProxy_->initialize(sessionId, testCredential_, docType_, encryptedCredentialKeys_)) {
+ hwProxy_.clear();
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error initializing hw proxy"));
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus IdentityCredential::deleteCredential(
vector<uint8_t>* outProofOfDeletionSignature) {
return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
@@ -93,6 +117,14 @@
ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
const vector<uint8_t>& challenge, bool includeChallenge,
vector<uint8_t>* outProofOfDeletionSignature) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
if (challenge.size() > 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
@@ -128,6 +160,14 @@
ndk::ScopedAStatus IdentityCredential::proveOwnership(
const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfOwnershipSignature) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
if (challenge.size() > 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
@@ -159,6 +199,14 @@
}
ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
if (!ephemeralPriv) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -186,11 +234,23 @@
ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
const vector<uint8_t>& publicKey) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
readerPublicKey_ = publicKey;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
if (!challenge) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -217,16 +277,22 @@
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
- std::unique_ptr<cppbor::Item> sessionTranscriptItem;
- if (sessionTranscript.size() > 0) {
- auto [item, _, message] = cppbor::parse(sessionTranscript);
- if (item == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "SessionTranscript contains invalid CBOR"));
- }
- sessionTranscriptItem = std::move(item);
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
}
+
+ // If in a session, ensure the passed-in session transcript matches the
+ // session transcript from the session.
+ if (session_) {
+ if (sessionTranscript != session_->getSessionTranscript()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
+ "In a session and passed-in SessionTranscript doesn't match the one "
+ "from the session"));
+ }
+ }
+
if (numStartRetrievalCalls_ > 0) {
if (sessionTranscript_ != sessionTranscript) {
LOG(ERROR) << "Session Transcript changed";
@@ -390,32 +456,36 @@
}
}
- // TODO: move this check to the TA
-#if 1
- // To prevent replay-attacks, we check that the public part of the ephemeral
- // key we previously created, is present in the DeviceEngagement part of
- // SessionTranscript as a COSE_Key, in uncompressed form.
- //
- // We do this by just searching for the X and Y coordinates.
- if (sessionTranscript.size() > 0) {
- auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
- if (!getXYSuccess) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "Error extracting X and Y from ePub"));
- }
- if (sessionTranscript.size() > 0 &&
- !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
- ePubX.size()) != nullptr &&
- memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
- ePubY.size()) != nullptr)) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "Did not find ephemeral public key's X and Y coordinates in "
- "SessionTranscript (make sure leading zeroes are not used)"));
+ if (session_) {
+ // If presenting in a session, the TA has already done this check.
+
+ } else {
+ // To prevent replay-attacks, we check that the public part of the ephemeral
+ // key we previously created, is present in the DeviceEngagement part of
+ // SessionTranscript as a COSE_Key, in uncompressed form.
+ //
+ // We do this by just searching for the X and Y coordinates.
+ //
+ // Would be nice to move this check to the TA.
+ if (sessionTranscript.size() > 0) {
+ auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+ if (!getXYSuccess) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Error extracting X and Y from ePub"));
+ }
+ if (sessionTranscript.size() > 0 &&
+ !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
+ ePubX.size()) != nullptr &&
+ memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
+ ePubY.size()) != nullptr)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Did not find ephemeral public key's X and Y coordinates in "
+ "SessionTranscript (make sure leading zeroes are not used)"));
+ }
}
}
-#endif
// itemsRequest: If non-empty, contains request data that may be signed by the
// reader. The content can be defined in the way appropriate for the
@@ -537,21 +607,38 @@
// Finally, pass info so the HMAC key can be derived and the TA can start
// creating the DeviceNameSpaces CBOR...
- if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) {
- // We expect the reader ephemeral public key to be same size and curve
- // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
- // won't work. So its length should be 65 bytes and it should be
- // starting with 0x04.
- if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Reader public key is not in expected format"));
+ if (!session_) {
+ if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
+ signingKeyBlob.size() > 0) {
+ // We expect the reader ephemeral public key to be same size and curve
+ // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+ // won't work. So its length should be 65 bytes and it should be
+ // starting with 0x04.
+ if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Reader public key is not in expected format"));
+ }
+ vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
+ if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
+ numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error starting retrieving entries"));
+ }
}
- vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
- if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
- numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+ } else {
+ if (session_->getSessionTranscript().size() > 0 &&
+ session_->getReaderEphemeralPublicKey().size() > 0 && signingKeyBlob.size() > 0) {
+ // Don't actually pass the reader ephemeral public key in, the TA will get
+ // it from the session object.
+ //
+ if (!hwProxy_->calcMacKey(sessionTranscript_, {}, signingKeyBlob, docType_,
+ numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error starting retrieving entries"));
+ }
}
}
@@ -665,6 +752,11 @@
ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
const string& nameSpace, const string& name, int32_t entrySize,
const vector<int32_t>& accessControlProfileIds) {
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
+
if (name.empty()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
@@ -785,6 +877,11 @@
ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
vector<uint8_t>* outContent) {
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
+
optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
if (!content) {
@@ -829,6 +926,11 @@
ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
vector<uint8_t>* outDeviceNameSpaces) {
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
+
if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
deviceNameSpacesMap_.add(currentNameSpace_,
std::move(currentNameSpaceDeviceNameSpacesMap_));
@@ -846,18 +948,23 @@
.c_str()));
}
- // If there's no signing key or no sessionTranscript or no reader ephemeral
- // public key, we return the empty MAC.
+ // If the TA calculated a MAC (it might not have), format it as a COSE_Mac0
+ //
optional<vector<uint8_t>> mac;
- if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
- readerPublicKey_.size() > 0) {
- optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
- if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
+ optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+
+ // The MAC not being set means an error occurred.
+ if (!digestToBeMaced) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Error generating digestToBeMaced"));
+ }
+ // Size 0 means that the MAC isn't set. If it's set, it has to be 32 bytes.
+ if (digestToBeMaced.value().size() != 0) {
+ if (digestToBeMaced.value().size() != 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error generating digestToBeMaced"));
+ "Unexpected size for digestToBeMaced"));
}
- // Now construct COSE_Mac0 from the returned MAC...
mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
}
@@ -868,6 +975,15 @@
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ ndk::ScopedAStatus status = ensureHwProxy();
+ if (!status.isOk()) {
+ return status;
+ }
+
time_t now = time(NULL);
optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
hwProxy_->generateSigningKeyPair(docType_, now);
@@ -885,9 +1001,18 @@
ndk::ScopedAStatus IdentityCredential::updateCredential(
shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
- sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
+ if (session_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
+ }
+ sp<SecureHardwareProvisioningProxy> provisioningHwProxy =
+ hwProxyFactory_->createProvisioningProxy();
+ if (!provisioningHwProxy) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating provisioning proxy"));
+ }
shared_ptr<WritableIdentityCredential> wc =
- ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
+ ndk::SharedRefBase::make<WritableIdentityCredential>(provisioningHwProxy, docType_,
testCredential_);
if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
diff --git a/identity/aidl/default/common/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h
index ef9d133..2935fb8 100644
--- a/identity/aidl/default/common/IdentityCredential.h
+++ b/identity/aidl/default/common/IdentityCredential.h
@@ -30,6 +30,7 @@
#include <cppbor.h>
#include "IdentityCredentialStore.h"
+#include "PresentationSession.h"
#include "SecureHardwareProxy.h"
namespace aidl::android::hardware::identity {
@@ -46,11 +47,11 @@
class IdentityCredential : public BnIdentityCredential {
public:
IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
- sp<SecureHardwarePresentationProxy> hwProxy,
- const vector<uint8_t>& credentialData)
+ const vector<uint8_t>& credentialData,
+ std::shared_ptr<PresentationSession> session)
: hwProxyFactory_(hwProxyFactory),
- hwProxy_(hwProxy),
credentialData_(credentialData),
+ session_(std::move(session)),
numStartRetrievalCalls_(0),
expectedDeviceNameSpacesSize_(0) {}
@@ -94,10 +95,13 @@
bool includeChallenge,
vector<uint8_t>* outProofOfDeletionSignature);
+ // Creates and initializes hwProxy_.
+ ndk::ScopedAStatus ensureHwProxy();
+
// Set by constructor
sp<SecureHardwareProxyFactory> hwProxyFactory_;
- sp<SecureHardwarePresentationProxy> hwProxy_;
vector<uint8_t> credentialData_;
+ shared_ptr<PresentationSession> session_;
int numStartRetrievalCalls_;
// Set by initialize()
@@ -105,6 +109,9 @@
bool testCredential_;
vector<uint8_t> encryptedCredentialKeys_;
+ // Set by ensureHwProxy()
+ sp<SecureHardwarePresentationProxy> hwProxy_;
+
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;
diff --git a/identity/aidl/default/common/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp
index e6b5466..4703ffe 100644
--- a/identity/aidl/default/common/IdentityCredentialStore.cpp
+++ b/identity/aidl/default/common/IdentityCredentialStore.cpp
@@ -20,6 +20,7 @@
#include "IdentityCredential.h"
#include "IdentityCredentialStore.h"
+#include "PresentationSession.h"
#include "WritableIdentityCredential.h"
namespace aidl::android::hardware::identity {
@@ -61,9 +62,8 @@
"Unsupported cipher suite"));
}
- sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
- shared_ptr<IdentityCredential> credential =
- ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy, credentialData);
+ shared_ptr<IdentityCredential> credential = ndk::SharedRefBase::make<IdentityCredential>(
+ hwProxyFactory_, credentialData, nullptr /* session */);
auto ret = credential->initialize();
if (ret != IIdentityCredentialStore::STATUS_OK) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -73,4 +73,25 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus IdentityCredentialStore::createPresentationSession(
+ CipherSuite cipherSuite, shared_ptr<IPresentationSession>* outSession) {
+ // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right now.
+ if (cipherSuite != CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED,
+ "Unsupported cipher suite"));
+ }
+
+ sp<SecureHardwareSessionProxy> hwProxy = hwProxyFactory_->createSessionProxy();
+ shared_ptr<PresentationSession> session =
+ ndk::SharedRefBase::make<PresentationSession>(hwProxyFactory_, hwProxy);
+ auto ret = session->initialize();
+ if (ret != IIdentityCredentialStore::STATUS_OK) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ int(ret), "Error initializing PresentationSession"));
+ }
+ *outSession = session;
+ return ndk::ScopedAStatus::ok();
+}
+
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/common/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h
index d35e632..77b894d 100644
--- a/identity/aidl/default/common/IdentityCredentialStore.h
+++ b/identity/aidl/default/common/IdentityCredentialStore.h
@@ -47,6 +47,9 @@
ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
shared_ptr<IIdentityCredential>* outCredential) override;
+ ndk::ScopedAStatus createPresentationSession(
+ CipherSuite cipherSuite, shared_ptr<IPresentationSession>* outSession) override;
+
private:
sp<SecureHardwareProxyFactory> hwProxyFactory_;
};
diff --git a/identity/aidl/default/common/PresentationSession.cpp b/identity/aidl/default/common/PresentationSession.cpp
new file mode 100644
index 0000000..fbd8972
--- /dev/null
+++ b/identity/aidl/default/common/PresentationSession.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PresentationSession"
+
+#include "PresentationSession.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include "FakeSecureHardwareProxy.h"
+#include "IdentityCredential.h"
+#include "PresentationSession.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::std::optional;
+
+using namespace ::android::hardware::identity;
+
+PresentationSession::~PresentationSession() {}
+
+int PresentationSession::initialize() {
+ if (!hwProxy_->initialize()) {
+ LOG(ERROR) << "hwProxy->initialize failed";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
+
+ optional<uint64_t> id = hwProxy_->getId();
+ if (!id) {
+ LOG(ERROR) << "Error getting id for session";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
+ id_ = id.value();
+
+ optional<vector<uint8_t>> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair();
+ if (!ephemeralKeyPriv) {
+ LOG(ERROR) << "Error getting ephemeral private key for session";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
+ optional<vector<uint8_t>> ephemeralKeyPair =
+ support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value());
+ if (!ephemeralKeyPair) {
+ LOG(ERROR) << "Error creating ephemeral key-pair";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
+ ephemeralKeyPair_ = ephemeralKeyPair.value();
+
+ optional<uint64_t> authChallenge = hwProxy_->getAuthChallenge();
+ if (!authChallenge) {
+ LOG(ERROR) << "Error getting authChallenge for session";
+ return IIdentityCredentialStore::STATUS_FAILED;
+ }
+ authChallenge_ = authChallenge.value();
+
+ return IIdentityCredentialStore::STATUS_OK;
+}
+
+ndk::ScopedAStatus PresentationSession::getEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
+ *outKeyPair = ephemeralKeyPair_;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PresentationSession::getAuthChallenge(int64_t* outChallenge) {
+ *outChallenge = authChallenge_;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PresentationSession::setReaderEphemeralPublicKey(
+ const vector<uint8_t>& publicKey) {
+ // We expect the reader ephemeral public key to be same size and curve
+ // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+ // won't work. So its length should be 65 bytes and it should be
+ // starting with 0x04.
+ if (publicKey.size() != 65 || publicKey[0] != 0x04) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Reader public key is not in expected format"));
+ }
+ readerPublicKey_ = publicKey;
+ vector<uint8_t> pubKeyP256(publicKey.begin() + 1, publicKey.end());
+ if (!hwProxy_->setReaderEphemeralPublicKey(pubKeyP256)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error setting readerEphemeralPublicKey for session"));
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PresentationSession::setSessionTranscript(
+ const vector<uint8_t>& sessionTranscript) {
+ sessionTranscript_ = sessionTranscript;
+ if (!hwProxy_->setSessionTranscript(sessionTranscript)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error setting SessionTranscript for session"));
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PresentationSession::getCredential(
+ const vector<uint8_t>& credentialData, shared_ptr<IIdentityCredential>* outCredential) {
+ shared_ptr<PresentationSession> p = ref<PresentationSession>();
+ shared_ptr<IdentityCredential> credential =
+ ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, credentialData, p);
+ int ret = credential->initialize();
+ if (ret != IIdentityCredentialStore::STATUS_OK) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ ret, "Error initializing IdentityCredential"));
+ }
+ *outCredential = std::move(credential);
+
+ return ndk::ScopedAStatus::ok();
+}
+
+uint64_t PresentationSession::getSessionId() {
+ return id_;
+}
+
+vector<uint8_t> PresentationSession::getSessionTranscript() {
+ return sessionTranscript_;
+}
+
+vector<uint8_t> PresentationSession::getReaderEphemeralPublicKey() {
+ return readerPublicKey_;
+}
+
+} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/common/PresentationSession.h b/identity/aidl/default/common/PresentationSession.h
new file mode 100644
index 0000000..76ca67b
--- /dev/null
+++ b/identity/aidl/default/common/PresentationSession.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H
+#define ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H
+
+#include <aidl/android/hardware/identity/BnPresentationSession.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <vector>
+
+#include <cppbor.h>
+
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::HardwareAuthToken;
+using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareSessionProxy;
+using ::std::vector;
+
+class PresentationSession : public BnPresentationSession {
+ public:
+ PresentationSession(sp<SecureHardwareProxyFactory> hwProxyFactory,
+ sp<SecureHardwareSessionProxy> hwProxy)
+ : hwProxyFactory_(std::move(hwProxyFactory)), hwProxy_(std::move(hwProxy)) {}
+
+ virtual ~PresentationSession();
+
+ // Creates ephemeral key and auth-challenge in TA. Returns a status code from
+ // IIdentityCredentialStore. Must be called right after construction.
+ int initialize();
+
+ uint64_t getSessionId();
+
+ vector<uint8_t> getSessionTranscript();
+ vector<uint8_t> getReaderEphemeralPublicKey();
+
+ // Methods from IPresentationSession follow.
+ ndk::ScopedAStatus getEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
+ ndk::ScopedAStatus getAuthChallenge(int64_t* outChallenge) override;
+ ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
+ ndk::ScopedAStatus setSessionTranscript(const vector<uint8_t>& sessionTranscript) override;
+
+ ndk::ScopedAStatus getCredential(const vector<uint8_t>& credentialData,
+ shared_ptr<IIdentityCredential>* outCredential) override;
+
+ private:
+ // Set by constructor
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
+ sp<SecureHardwareSessionProxy> hwProxy_;
+
+ // Set by initialize()
+ uint64_t id_;
+ vector<uint8_t> ephemeralKeyPair_;
+ uint64_t authChallenge_;
+
+ // Set by setReaderEphemeralPublicKey()
+ vector<uint8_t> readerPublicKey_;
+
+ // Set by setSessionTranscript()
+ vector<uint8_t> sessionTranscript_;
+};
+
+} // namespace aidl::android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H
diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h
index a1ed1ef..a580444 100644
--- a/identity/aidl/default/common/SecureHardwareProxy.h
+++ b/identity/aidl/default/common/SecureHardwareProxy.h
@@ -42,6 +42,7 @@
// Forward declare.
//
class SecureHardwareProvisioningProxy;
+class SecureHardwareSessionProxy;
class SecureHardwarePresentationProxy;
// This is a class used to create proxies.
@@ -52,6 +53,7 @@
virtual ~SecureHardwareProxyFactory() {}
virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+ virtual sp<SecureHardwareSessionProxy> createSessionProxy() = 0;
virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
};
@@ -64,8 +66,12 @@
virtual bool initialize(bool testCredential) = 0;
- virtual bool initializeForUpdate(bool testCredential, string docType,
- vector<uint8_t> encryptedCredentialKeys) = 0;
+ virtual bool initializeForUpdate(bool testCredential, const string& docType,
+ const vector<uint8_t>& encryptedCredentialKeys) = 0;
+
+ virtual optional<uint32_t> getId() = 0;
+
+ virtual bool shutdown() = 0;
// Returns public key certificate chain with attestation.
//
@@ -76,7 +82,7 @@
virtual optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
const vector<uint8_t>& applicationId) = 0;
- virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+ virtual bool startPersonalization(int accessControlProfileCount, const vector<int>& entryCounts,
const string& docType,
size_t expectedProofOfProvisioningSize) = 0;
@@ -98,8 +104,6 @@
// Returns encryptedCredentialKeys (80 bytes).
virtual optional<vector<uint8_t>> finishGetCredentialData(const string& docType) = 0;
-
- virtual bool shutdown() = 0;
};
enum AccessCheckResult {
@@ -110,6 +114,30 @@
kReaderAuthenticationFailed,
};
+// The proxy used for sessions.
+//
+class SecureHardwareSessionProxy : public RefBase {
+ public:
+ SecureHardwareSessionProxy() {}
+
+ virtual ~SecureHardwareSessionProxy() {}
+
+ virtual bool initialize() = 0;
+
+ virtual optional<uint32_t> getId() = 0;
+
+ virtual bool shutdown() = 0;
+
+ virtual optional<uint64_t> getAuthChallenge() = 0;
+
+ // Returns private key
+ virtual optional<vector<uint8_t>> getEphemeralKeyPair() = 0;
+
+ virtual bool setReaderEphemeralPublicKey(const vector<uint8_t>& readerEphemeralPublicKey) = 0;
+
+ virtual bool setSessionTranscript(const vector<uint8_t>& sessionTranscript) = 0;
+};
+
// The proxy used for presentation.
//
class SecureHardwarePresentationProxy : public RefBase {
@@ -117,12 +145,16 @@
SecureHardwarePresentationProxy() {}
virtual ~SecureHardwarePresentationProxy() {}
- virtual bool initialize(bool testCredential, string docType,
- vector<uint8_t> encryptedCredentialKeys) = 0;
+ virtual bool initialize(uint32_t sessionId, bool testCredential, const string& docType,
+ const vector<uint8_t>& encryptedCredentialKeys) = 0;
+
+ virtual optional<uint32_t> getId() = 0;
+
+ virtual bool shutdown() = 0;
// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
- virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
- time_t now) = 0;
+ virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(
+ const string& docType, time_t now) = 0;
// Returns private key
virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
@@ -174,8 +206,6 @@
virtual optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
const vector<uint8_t>& challenge,
size_t proofOfOwnershipCborSize) = 0;
-
- virtual bool shutdown() = 0;
};
} // namespace android::hardware::identity