From 1eb12b29728adcbbe5b8694f671c67b8a624fe4a Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Sat, 11 Sep 2021 13:59:43 -0400 Subject: 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 --- identity/aidl/default/Android.bp | 10 +- identity/aidl/default/EicOpsImpl.cc | 30 +- identity/aidl/default/EicTests.cpp | 3 +- identity/aidl/default/FakeSecureHardwareProxy.cpp | 406 ++++++++++++++++++--- identity/aidl/default/FakeSecureHardwareProxy.h | 103 +++++- .../android.hardware.identity_credential.xml | 2 +- .../aidl/default/common/IdentityCredential.cpp | 241 +++++++++--- identity/aidl/default/common/IdentityCredential.h | 15 +- .../default/common/IdentityCredentialStore.cpp | 27 +- .../aidl/default/common/IdentityCredentialStore.h | 3 + .../aidl/default/common/PresentationSession.cpp | 149 ++++++++ identity/aidl/default/common/PresentationSession.h | 83 +++++ identity/aidl/default/common/SecureHardwareProxy.h | 52 ++- identity/aidl/default/identity-default.xml | 2 +- identity/aidl/default/libeic/EicCommon.h | 3 + identity/aidl/default/libeic/EicOps.h | 12 + identity/aidl/default/libeic/EicPresentation.c | 108 +++++- identity/aidl/default/libeic/EicPresentation.h | 22 +- identity/aidl/default/libeic/EicProvisioning.c | 35 ++ identity/aidl/default/libeic/EicProvisioning.h | 7 + identity/aidl/default/libeic/EicSession.c | 120 ++++++ identity/aidl/default/libeic/EicSession.h | 73 ++++ identity/aidl/default/libeic/libeic.h | 3 +- 23 files changed, 1360 insertions(+), 149 deletions(-) create mode 100644 identity/aidl/default/common/PresentationSession.cpp create mode 100644 identity/aidl/default/common/PresentationSession.h create mode 100644 identity/aidl/default/libeic/EicSession.c create mode 100644 identity/aidl/default/libeic/EicSession.h (limited to 'identity/aidl/default') diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp index 3de8d30148..ca24afa6cc 100644 --- a/identity/aidl/default/Android.bp +++ b/identity/aidl/default/Android.bp @@ -13,6 +13,7 @@ cc_library_static { srcs: [ "common/IdentityCredential.cpp", "common/IdentityCredentialStore.cpp", + "common/PresentationSession.cpp", "common/WritableIdentityCredential.cpp", ], export_include_dirs: [ @@ -39,8 +40,8 @@ cc_library_static { "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", - "android.hardware.identity-V3-ndk", - "android.hardware.keymaster-V3-ndk", + "android.hardware.identity-V4-ndk", + "android.hardware.keymaster-V4-ndk", ], } @@ -49,6 +50,7 @@ cc_library_static { vendor_available: true, srcs: [ "libeic/EicCbor.c", + "libeic/EicSession.c", "libeic/EicPresentation.c", "libeic/EicProvisioning.c", "EicOpsImpl.cc", @@ -100,8 +102,8 @@ cc_binary { "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", - "android.hardware.identity-V3-ndk", - "android.hardware.keymaster-V3-ndk", + "android.hardware.identity-V4-ndk", + "android.hardware.keymaster-V4-ndk", "android.hardware.identity-libeic-hal-common", "android.hardware.identity-libeic-library", ], diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc index 8ec4cc9b58..c98a91ebc3 100644 --- a/identity/aidl/default/EicOpsImpl.cc +++ b/identity/aidl/default/EicOpsImpl.cc @@ -20,9 +20,13 @@ #include #include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + #include #include -#include #include @@ -63,6 +67,11 @@ size_t eicStrLen(const char* s) { return strlen(s); } +void* eicMemMem(const uint8_t* haystack, size_t haystackLen, const uint8_t* needle, + size_t needleLen) { + return memmem(haystack, haystackLen, needle, needleLen); +} + int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) { return CRYPTO_memcmp(s1, s2, n); } @@ -117,6 +126,25 @@ bool eicOpsRandom(uint8_t* buf, size_t numBytes) { return true; } +bool eicNextId(uint32_t* id) { + uint32_t oldId = *id; + uint32_t newId = 0; + + do { + union { + uint8_t value8; + uint32_t value32; + } value; + if (!eicOpsRandom(&value.value8, sizeof(value))) { + return false; + } + newId = value.value32; + } while (newId == oldId && newId == 0); + + *id = newId; + return true; +} + bool eicOpsEncryptAes128Gcm( const uint8_t* key, // Must be 16 bytes const uint8_t* nonce, // Must be 12 bytes diff --git a/identity/aidl/default/EicTests.cpp b/identity/aidl/default/EicTests.cpp index a28080d009..7b69b75acd 100644 --- a/identity/aidl/default/EicTests.cpp +++ b/identity/aidl/default/EicTests.cpp @@ -66,7 +66,8 @@ TEST(EicTest, AccessControlIsEnforced) { // Then present data from it... // FakeSecureHardwarePresentationProxy presentationProxy; - ASSERT_TRUE(presentationProxy.initialize(isTestCredential, docType, credData.value())); + ASSERT_TRUE(presentationProxy.initialize(0 /* sessionId */, isTestCredential, docType, + credData.value())); AccessCheckResult res = presentationProxy.startRetrieveEntryValue(nameSpace, name, 1, content.size(), acpIds); ASSERT_EQ(res, AccessCheckResult::kNoAccessControlProfiles); diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp index f0307dc324..91e634c0c3 100644 --- a/identity/aidl/default/FakeSecureHardwareProxy.cpp +++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -52,38 +53,110 @@ namespace android::hardware::identity { // ---------------------------------------------------------------------- -FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {} +// The singleton EicProvisioning object used everywhere. +// +EicProvisioning FakeSecureHardwareProvisioningProxy::ctx_; -FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {} - -bool FakeSecureHardwareProvisioningProxy::shutdown() { - LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; - return true; +FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() { + if (id_ != 0) { + shutdown(); + } } bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) { - LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): " - << sizeof(EicProvisioning); - return eicProvisioningInit(&ctx_, testCredential); + if (id_ != 0) { + LOG(WARNING) << "Proxy is already initialized"; + return false; + } + bool initialized = eicProvisioningInit(&ctx_, testCredential); + if (!initialized) { + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "Error getting id"; + return false; + } + id_ = id.value(); + return true; } bool FakeSecureHardwareProvisioningProxy::initializeForUpdate( - bool testCredential, string docType, vector encryptedCredentialKeys) { - return eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(), - docType.size(), - encryptedCredentialKeys.data(), - encryptedCredentialKeys.size()); + bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) { + if (id_ != 0) { + LOG(WARNING) << "Proxy is already initialized"; + return false; + } + bool initialized = eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(), + docType.size(), encryptedCredentialKeys.data(), + encryptedCredentialKeys.size()); + if (!initialized) { + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "Error getting id"; + return false; + } + id_ = id.value(); + return true; +} + +optional FakeSecureHardwareProvisioningProxy::getId() { + uint32_t id; + if (!eicProvisioningGetId(&ctx_, &id)) { + return std::nullopt; + } + return id; +} + +bool FakeSecureHardwareProvisioningProxy::validateId(const string& callerName) { + if (id_ == 0) { + LOG(WARNING) << "FakeSecureHardwareProvisioningProxy::" << callerName + << ": While validating expected id is 0"; + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "FakeSecureHardwareProvisioningProxy::" << callerName + << ": Error getting id for validating"; + return false; + } + if (id.value() != id_) { + LOG(WARNING) << "FakeSecureHardwareProvisioningProxy::" << callerName + << ": While validating expected id " << id_ << " but got " << id.value(); + return false; + } + return true; +} + +bool FakeSecureHardwareProvisioningProxy::shutdown() { + bool validated = validateId(__func__); + id_ = 0; + if (!validated) { + return false; + } + if (!eicProvisioningShutdown(&ctx_)) { + LOG(INFO) << "Error shutting down provisioning"; + return false; + } + return true; } // Returns public key certificate. optional> FakeSecureHardwareProvisioningProxy::createCredentialKey( const vector& challenge, const vector& applicationId) { + if (!validateId(__func__)) { + return std::nullopt; + } + uint8_t publicKeyCert[4096]; size_t publicKeyCertSize = sizeof publicKeyCert; if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(), applicationId.data(), applicationId.size(), publicKeyCert, &publicKeyCertSize)) { - return {}; + return std::nullopt; } vector pubKeyCert(publicKeyCertSize); memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize); @@ -91,8 +164,11 @@ optional> FakeSecureHardwareProvisioningProxy::createCredentialK } bool FakeSecureHardwareProvisioningProxy::startPersonalization( - int accessControlProfileCount, vector entryCounts, const string& docType, + int accessControlProfileCount, const vector& entryCounts, const string& docType, size_t expectedProofOfProvisioningSize) { + if (!validateId(__func__)) { + return false; + } if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(), @@ -108,13 +184,17 @@ bool FakeSecureHardwareProvisioningProxy::startPersonalization( optional> FakeSecureHardwareProvisioningProxy::addAccessControlProfile( int id, const vector& readerCertificate, bool userAuthenticationRequired, uint64_t timeoutMillis, uint64_t secureUserId) { + if (!validateId(__func__)) { + return std::nullopt; + } + vector mac(28); uint8_t scratchSpace[512]; if (!eicProvisioningAddAccessControlProfile( &ctx_, id, readerCertificate.data(), readerCertificate.size(), userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(), scratchSpace, sizeof(scratchSpace))) { - return {}; + return std::nullopt; } return mac; } @@ -122,6 +202,10 @@ optional> FakeSecureHardwareProvisioningProxy::addAccessControlP bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector& accessControlProfileIds, const string& nameSpace, const string& name, uint64_t entrySize) { + if (!validateId(__func__)) { + return false; + } + uint8_t scratchSpace[512]; vector uint8AccessControlProfileIds; for (size_t i = 0; i < accessControlProfileIds.size(); i++) { @@ -138,6 +222,10 @@ bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector& acces optional> FakeSecureHardwareProvisioningProxy::addEntryValue( const vector& accessControlProfileIds, const string& nameSpace, const string& name, const vector& content) { + if (!validateId(__func__)) { + return std::nullopt; + } + vector eicEncryptedContent; uint8_t scratchSpace[512]; vector uint8AccessControlProfileIds; @@ -150,16 +238,20 @@ optional> FakeSecureHardwareProvisioningProxy::addEntryValue( &ctx_, uint8AccessControlProfileIds.data(), uint8AccessControlProfileIds.size(), nameSpace.c_str(), nameSpace.size(), name.c_str(), name.size(), content.data(), content.size(), eicEncryptedContent.data(), scratchSpace, sizeof(scratchSpace))) { - return {}; + return std::nullopt; } return eicEncryptedContent; } // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). optional> FakeSecureHardwareProvisioningProxy::finishAddingEntries() { + if (!validateId(__func__)) { + return std::nullopt; + } + vector signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) { - return {}; + return std::nullopt; } return signatureOfToBeSigned; } @@ -167,11 +259,15 @@ optional> FakeSecureHardwareProvisioningProxy::finishAddingEntri // Returns encryptedCredentialKeys. optional> FakeSecureHardwareProvisioningProxy::finishGetCredentialData( const string& docType) { + if (!validateId(__func__)) { + return std::nullopt; + } + vector encryptedCredentialKeys(116); size_t size = encryptedCredentialKeys.size(); if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(), docType.size(), encryptedCredentialKeys.data(), &size)) { - return {}; + return std::nullopt; } encryptedCredentialKeys.resize(size); return encryptedCredentialKeys; @@ -179,21 +275,200 @@ optional> FakeSecureHardwareProvisioningProxy::finishGetCredenti // ---------------------------------------------------------------------- -FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {} +// The singleton EicSession object used everywhere. +// +EicSession FakeSecureHardwareSessionProxy::ctx_; + +FakeSecureHardwareSessionProxy::~FakeSecureHardwareSessionProxy() { + if (id_ != 0) { + shutdown(); + } +} + +bool FakeSecureHardwareSessionProxy::initialize() { + if (id_ != 0) { + LOG(WARNING) << "Proxy is already initialized"; + return false; + } + bool initialized = eicSessionInit(&ctx_); + if (!initialized) { + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "Error getting id"; + return false; + } + id_ = id.value(); + return true; +} + +optional FakeSecureHardwareSessionProxy::getId() { + uint32_t id; + if (!eicSessionGetId(&ctx_, &id)) { + return std::nullopt; + } + return id; +} + +bool FakeSecureHardwareSessionProxy::shutdown() { + bool validated = validateId(__func__); + id_ = 0; + if (!validated) { + return false; + } + if (!eicSessionShutdown(&ctx_)) { + LOG(INFO) << "Error shutting down session"; + return false; + } + return true; +} + +bool FakeSecureHardwareSessionProxy::validateId(const string& callerName) { + if (id_ == 0) { + LOG(WARNING) << "FakeSecureHardwareSessionProxy::" << callerName + << ": While validating expected id is 0"; + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "FakeSecureHardwareSessionProxy::" << callerName + << ": Error getting id for validating"; + return false; + } + if (id.value() != id_) { + LOG(WARNING) << "FakeSecureHardwareSessionProxy::" << callerName + << ": While validating expected id " << id_ << " but got " << id.value(); + return false; + } + return true; +} -FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {} +optional FakeSecureHardwareSessionProxy::getAuthChallenge() { + if (!validateId(__func__)) { + return std::nullopt; + } -bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType, - vector encryptedCredentialKeys) { - LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): " - << sizeof(EicPresentation); - return eicPresentationInit(&ctx_, testCredential, docType.c_str(), docType.size(), - encryptedCredentialKeys.data(), encryptedCredentialKeys.size()); + uint64_t authChallenge; + if (!eicSessionGetAuthChallenge(&ctx_, &authChallenge)) { + return std::nullopt; + } + return authChallenge; +} + +optional> FakeSecureHardwareSessionProxy::getEphemeralKeyPair() { + if (!validateId(__func__)) { + return std::nullopt; + } + + vector priv(EIC_P256_PRIV_KEY_SIZE); + if (!eicSessionGetEphemeralKeyPair(&ctx_, priv.data())) { + return std::nullopt; + } + return priv; +} + +bool FakeSecureHardwareSessionProxy::setReaderEphemeralPublicKey( + const vector& readerEphemeralPublicKey) { + if (!validateId(__func__)) { + return false; + } + + return eicSessionSetReaderEphemeralPublicKey(&ctx_, readerEphemeralPublicKey.data()); +} + +bool FakeSecureHardwareSessionProxy::setSessionTranscript( + const vector& sessionTranscript) { + if (!validateId(__func__)) { + return false; + } + + return eicSessionSetSessionTranscript(&ctx_, sessionTranscript.data(), + sessionTranscript.size()); +} + +// ---------------------------------------------------------------------- + +// The singleton EicPresentation object used everywhere. +// +EicPresentation FakeSecureHardwarePresentationProxy::ctx_; + +FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() { + if (id_ != 0) { + shutdown(); + } +} + +bool FakeSecureHardwarePresentationProxy::initialize( + uint32_t sessionId, bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) { + if (id_ != 0) { + LOG(WARNING) << "Proxy is already initialized"; + return false; + } + bool initialized = + eicPresentationInit(&ctx_, sessionId, testCredential, docType.c_str(), docType.size(), + encryptedCredentialKeys.data(), encryptedCredentialKeys.size()); + if (!initialized) { + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "Error getting id"; + return false; + } + id_ = id.value(); + return true; +} + +optional FakeSecureHardwarePresentationProxy::getId() { + uint32_t id; + if (!eicPresentationGetId(&ctx_, &id)) { + return std::nullopt; + } + return id; +} + +bool FakeSecureHardwarePresentationProxy::validateId(const string& callerName) { + if (id_ == 0) { + LOG(WARNING) << "FakeSecureHardwarePresentationProxy::" << callerName + << ": While validating expected id is 0"; + return false; + } + optional id = getId(); + if (!id) { + LOG(WARNING) << "FakeSecureHardwarePresentationProxy::" << callerName + << ": Error getting id for validating"; + return false; + } + if (id.value() != id_) { + LOG(WARNING) << "FakeSecureHardwarePresentationProxy::" << callerName + << ": While validating expected id " << id_ << " but got " << id.value(); + return false; + } + return true; +} + +bool FakeSecureHardwarePresentationProxy::shutdown() { + bool validated = validateId(__func__); + id_ = 0; + if (!validated) { + return false; + } + if (!eicPresentationShutdown(&ctx_)) { + LOG(INFO) << "Error shutting down presentation"; + return false; + } + return true; } // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) optional, vector>> -FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) { +FakeSecureHardwarePresentationProxy::generateSigningKeyPair(const string& docType, time_t now) { + if (!validateId(__func__)) { + return std::nullopt; + } + uint8_t publicKeyCert[512]; size_t publicKeyCertSize = sizeof(publicKeyCert); vector signingKeyBlob(60); @@ -201,7 +476,7 @@ FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), docType.size(), now, publicKeyCert, &publicKeyCertSize, signingKeyBlob.data())) { - return {}; + return std::nullopt; } vector cert; @@ -213,33 +488,44 @@ FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time // Returns private key optional> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() { + if (!validateId(__func__)) { + return std::nullopt; + } + vector priv(EIC_P256_PRIV_KEY_SIZE); if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) { - return {}; + return std::nullopt; } return priv; } optional FakeSecureHardwarePresentationProxy::createAuthChallenge() { + if (!validateId(__func__)) { + return std::nullopt; + } + uint64_t challenge; if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) { - return {}; + return std::nullopt; } return challenge; } -bool FakeSecureHardwarePresentationProxy::shutdown() { - LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; - return true; -} - bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector& certX509) { + if (!validateId(__func__)) { + return false; + } + return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size()); } bool FakeSecureHardwarePresentationProxy::validateRequestMessage( const vector& sessionTranscript, const vector& requestMessage, int coseSignAlg, const vector& readerSignatureOfToBeSigned) { + if (!validateId(__func__)) { + return false; + } + return eicPresentationValidateRequestMessage( &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(), requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(), @@ -251,6 +537,10 @@ bool FakeSecureHardwarePresentationProxy::setAuthToken( int hardwareAuthenticatorType, uint64_t timeStamp, const vector& mac, uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp, int verificationTokenSecurityLevel, const vector& verificationTokenMac) { + if (!validateId(__func__)) { + return false; + } + return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(), verificationTokenChallenge, verificationTokenTimestamp, @@ -261,6 +551,10 @@ bool FakeSecureHardwarePresentationProxy::setAuthToken( optional FakeSecureHardwarePresentationProxy::validateAccessControlProfile( int id, const vector& readerCertificate, bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId, const vector& mac) { + if (!validateId(__func__)) { + return std::nullopt; + } + bool accessGranted = false; uint8_t scratchSpace[512]; if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(), @@ -268,12 +562,16 @@ optional FakeSecureHardwarePresentationProxy::validateAccessControlProfile userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(), &accessGranted, scratchSpace, sizeof(scratchSpace))) { - return {}; + return std::nullopt; } return accessGranted; } bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() { + if (!validateId(__func__)) { + return false; + } + return eicPresentationStartRetrieveEntries(&ctx_); } @@ -281,6 +579,10 @@ bool FakeSecureHardwarePresentationProxy::calcMacKey( const vector& sessionTranscript, const vector& readerEphemeralPublicKey, const vector& signingKeyBlob, const string& docType, unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) { + if (!validateId(__func__)) { + return false; + } + if (signingKeyBlob.size() != 60) { eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size()); return false; @@ -294,6 +596,10 @@ bool FakeSecureHardwarePresentationProxy::calcMacKey( AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue( const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, int32_t entrySize, const vector& accessControlProfileIds) { + if (!validateId(__func__)) { + return AccessCheckResult::kFailed; + } + uint8_t scratchSpace[512]; vector uint8AccessControlProfileIds; for (size_t i = 0; i < accessControlProfileIds.size(); i++) { @@ -324,6 +630,10 @@ AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue( optional> FakeSecureHardwarePresentationProxy::retrieveEntryValue( const vector& encryptedContent, const string& nameSpace, const string& name, const vector& accessControlProfileIds) { + if (!validateId(__func__)) { + return std::nullopt; + } + uint8_t scratchSpace[512]; vector uint8AccessControlProfileIds; for (size_t i = 0; i < accessControlProfileIds.size(); i++) { @@ -337,16 +647,20 @@ optional> FakeSecureHardwarePresentationProxy::retrieveEntryValu nameSpace.c_str(), nameSpace.size(), name.c_str(), name.size(), uint8AccessControlProfileIds.data(), uint8AccessControlProfileIds.size(), scratchSpace, sizeof(scratchSpace))) { - return {}; + return std::nullopt; } return content; } optional> FakeSecureHardwarePresentationProxy::finishRetrieval() { + if (!validateId(__func__)) { + return std::nullopt; + } + vector mac(32); size_t macSize = 32; if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) { - return {}; + return std::nullopt; } mac.resize(macSize); return mac; @@ -355,11 +669,15 @@ optional> FakeSecureHardwarePresentationProxy::finishRetrieval() optional> FakeSecureHardwarePresentationProxy::deleteCredential( const string& docType, const vector& challenge, bool includeChallenge, size_t proofOfDeletionCborSize) { + if (!validateId(__func__)) { + return std::nullopt; + } + vector signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), docType.size(), challenge.data(), challenge.size(), includeChallenge, proofOfDeletionCborSize, signatureOfToBeSigned.data())) { - return {}; + return std::nullopt; } return signatureOfToBeSigned; } @@ -367,11 +685,15 @@ optional> FakeSecureHardwarePresentationProxy::deleteCredential( optional> FakeSecureHardwarePresentationProxy::proveOwnership( const string& docType, bool testCredential, const vector& challenge, size_t proofOfOwnershipCborSize) { + if (!validateId(__func__)) { + return std::nullopt; + } + vector signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), docType.size(), testCredential, challenge.data(), challenge.size(), proofOfOwnershipCborSize, signatureOfToBeSigned.data())) { - return {}; + return std::nullopt; } return signatureOfToBeSigned; } diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h index 6852c1a979..df98c7a121 100644 --- a/identity/aidl/default/FakeSecureHardwareProxy.h +++ b/identity/aidl/default/FakeSecureHardwareProxy.h @@ -27,21 +27,23 @@ namespace android::hardware::identity { // class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy { public: - FakeSecureHardwareProvisioningProxy(); + FakeSecureHardwareProvisioningProxy() = default; virtual ~FakeSecureHardwareProvisioningProxy(); bool initialize(bool testCredential) override; - bool initializeForUpdate(bool testCredential, string docType, - vector encryptedCredentialKeys) override; + bool initializeForUpdate(bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) override; bool shutdown() override; + optional getId() override; + // Returns public key certificate. optional> createCredentialKey(const vector& challenge, const vector& applicationId) override; - bool startPersonalization(int accessControlProfileCount, vector entryCounts, + bool startPersonalization(int accessControlProfileCount, const vector& entryCounts, const string& docType, size_t expectedProofOfProvisioningSize) override; @@ -67,21 +69,81 @@ class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningPro optional> finishGetCredentialData(const string& docType) override; protected: - EicProvisioning ctx_; + // See docs for id_. + // + bool validateId(const string& callerName); + + // We use a singleton libeic object, shared by all proxy instances. This is to + // properly simulate a situation where libeic is used on constrained hardware + // with only enough RAM for a single instance of the libeic object. + // + static EicProvisioning ctx_; + + // On the HAL side we keep track of the ID that was assigned to the libeic object + // created in secure hardware. For every call into libeic we validate that this + // identifier matches what is on the secure side. This is what the validateId() + // method does. + // + uint32_t id_ = 0; +}; + +// This implementation uses libEmbeddedIC in-process. +// +class FakeSecureHardwareSessionProxy : public SecureHardwareSessionProxy { + public: + FakeSecureHardwareSessionProxy() = default; + virtual ~FakeSecureHardwareSessionProxy(); + + bool initialize() override; + + bool shutdown() override; + + optional getId() override; + + optional getAuthChallenge() override; + + // Returns private key + optional> getEphemeralKeyPair() override; + + bool setReaderEphemeralPublicKey(const vector& readerEphemeralPublicKey) override; + + bool setSessionTranscript(const vector& sessionTranscript) override; + + protected: + // See docs for id_. + // + bool validateId(const string& callerName); + + // We use a singleton libeic object, shared by all proxy instances. This is to + // properly simulate a situation where libeic is used on constrained hardware + // with only enough RAM for a single instance of the libeic object. + // + static EicSession ctx_; + + // On the HAL side we keep track of the ID that was assigned to the libeic object + // created in secure hardware. For every call into libeic we validate that this + // identifier matches what is on the secure side. This is what the validateId() + // method does. + // + uint32_t id_ = 0; }; // This implementation uses libEmbeddedIC in-process. // class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy { public: - FakeSecureHardwarePresentationProxy(); + FakeSecureHardwarePresentationProxy() = default; virtual ~FakeSecureHardwarePresentationProxy(); - bool initialize(bool testCredential, string docType, - vector encryptedCredentialKeys) override; + bool initialize(uint32_t sessionId, bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) override; + + bool shutdown() override; + + optional getId() override; // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) - optional, vector>> generateSigningKeyPair(string docType, + optional, vector>> generateSigningKeyPair(const string& docType, time_t now) override; // Returns private key @@ -133,10 +195,23 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro const vector& challenge, size_t proofOfOwnershipCborSize) override; - bool shutdown() override; - protected: - EicPresentation ctx_; + // See docs for id_. + // + bool validateId(const string& callerName); + + // We use a singleton libeic object, shared by all proxy instances. This is to + // properly simulate a situation where libeic is used on constrained hardware + // with only enough RAM for a single instance of the libeic object. + // + static EicPresentation ctx_; + + // On the HAL side we keep track of the ID that was assigned to the libeic object + // created in secure hardware. For every call into libeic we validate that this + // identifier matches what is on the secure side. This is what the validateId() + // method does. + // + uint32_t id_ = 0; }; // Factory implementation. @@ -150,6 +225,10 @@ class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory { return new FakeSecureHardwareProvisioningProxy(); } + sp createSessionProxy() override { + return new FakeSecureHardwareSessionProxy(); + } + sp createPresentationProxy() override { return new FakeSecureHardwarePresentationProxy(); } diff --git a/identity/aidl/default/android.hardware.identity_credential.xml b/identity/aidl/default/android.hardware.identity_credential.xml index 5149792b7f..20b2710e1e 100644 --- a/identity/aidl/default/android.hardware.identity_credential.xml +++ b/identity/aidl/default/android.hardware.identity_credential.xml @@ -14,5 +14,5 @@ limitations under the License. --> - + diff --git a/identity/aidl/default/common/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp index 95557b5c9d..7678ecb918 100644 --- a/identity/aidl/default/common/IdentityCredential.cpp +++ b/identity/aidl/default/common/IdentityCredential.cpp @@ -72,14 +72,38 @@ int IdentityCredential::initialize() { 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* outProofOfDeletionSignature) { return deleteCredentialCommon({}, false, outProofOfDeletionSignature); @@ -93,6 +117,14 @@ ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge( ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon( const vector& challenge, bool includeChallenge, vector* 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::deleteCredentialCommon( ndk::ScopedAStatus IdentityCredential::proveOwnership( const vector& challenge, vector* 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::proveOwnership( } ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector* 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> ephemeralPriv = hwProxy_->createEphemeralKeyPair(); if (!ephemeralPriv) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( @@ -186,11 +234,23 @@ ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector* o ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey( const vector& 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 challenge = hwProxy_->createAuthChallenge(); if (!challenge) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( @@ -217,16 +277,22 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( const HardwareAuthToken& authToken, const vector& itemsRequest, const vector& signingKeyBlob, const vector& sessionTranscript, const vector& readerSignature, const vector& requestCounts) { - std::unique_ptr sessionTranscriptItem; - if (sessionTranscript.size() > 0) { - auto [item, _, message] = cppbor::parse(sessionTranscript); - if (item == nullptr) { + 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_INVALID_DATA, - "SessionTranscript contains invalid CBOR")); + IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH, + "In a session and passed-in SessionTranscript doesn't match the one " + "from the session")); } - sessionTranscriptItem = std::move(item); } + if (numStartRetrievalCalls_ > 0) { if (sessionTranscript_ != sessionTranscript) { LOG(ERROR) << "Session Transcript changed"; @@ -390,32 +456,36 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } } - // 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 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( // 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 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 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 @@ void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileM ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( const string& nameSpace, const string& name, int32_t entrySize, const vector& 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::startRetrieveEntryValue( ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector& encryptedContent, vector* outContent) { + ndk::ScopedAStatus status = ensureHwProxy(); + if (!status.isOk()) { + return status; + } + optional> content = hwProxy_->retrieveEntryValue( encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_); if (!content) { @@ -829,6 +926,11 @@ ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector& ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector* outMac, vector* outDeviceNameSpaces) { + ndk::ScopedAStatus status = ensureHwProxy(); + if (!status.isOk()) { + return status; + } + if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) { deviceNameSpacesMap_.add(currentNameSpace_, std::move(currentNameSpaceDeviceNameSpacesMap_)); @@ -846,18 +948,23 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector* outMac, .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> mac; - if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 && - readerPublicKey_.size() > 0) { - optional> digestToBeMaced = hwProxy_->finishRetrieval(); - if (!digestToBeMaced || digestToBeMaced.value().size() != 32) { + optional> 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::finishRetrieval(vector* outMac, ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair( vector* 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, vector>> pair = hwProxy_->generateSigningKeyPair(docType_, now); @@ -885,9 +1001,18 @@ ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair( ndk::ScopedAStatus IdentityCredential::updateCredential( shared_ptr* outWritableCredential) { - sp hwProxy = hwProxyFactory_->createProvisioningProxy(); + if (session_) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session")); + } + sp provisioningHwProxy = + hwProxyFactory_->createProvisioningProxy(); + if (!provisioningHwProxy) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error creating provisioning proxy")); + } shared_ptr wc = - ndk::SharedRefBase::make(hwProxy, docType_, + ndk::SharedRefBase::make(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 ef9d13351c..2935fb80a7 100644 --- a/identity/aidl/default/common/IdentityCredential.h +++ b/identity/aidl/default/common/IdentityCredential.h @@ -30,6 +30,7 @@ #include #include "IdentityCredentialStore.h" +#include "PresentationSession.h" #include "SecureHardwareProxy.h" namespace aidl::android::hardware::identity { @@ -46,11 +47,11 @@ using ::std::vector; class IdentityCredential : public BnIdentityCredential { public: IdentityCredential(sp hwProxyFactory, - sp hwProxy, - const vector& credentialData) + const vector& credentialData, + std::shared_ptr session) : hwProxyFactory_(hwProxyFactory), - hwProxy_(hwProxy), credentialData_(credentialData), + session_(std::move(session)), numStartRetrievalCalls_(0), expectedDeviceNameSpacesSize_(0) {} @@ -94,10 +95,13 @@ class IdentityCredential : public BnIdentityCredential { bool includeChallenge, vector* outProofOfDeletionSignature); + // Creates and initializes hwProxy_. + ndk::ScopedAStatus ensureHwProxy(); + // Set by constructor sp hwProxyFactory_; - sp hwProxy_; vector credentialData_; + shared_ptr session_; int numStartRetrievalCalls_; // Set by initialize() @@ -105,6 +109,9 @@ class IdentityCredential : public BnIdentityCredential { bool testCredential_; vector encryptedCredentialKeys_; + // Set by ensureHwProxy() + sp hwProxy_; + // Set by createEphemeralKeyPair() vector ephemeralPublicKey_; diff --git a/identity/aidl/default/common/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp index e6b5466096..4703ffe646 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 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential( "Unsupported cipher suite")); } - sp hwProxy = hwProxyFactory_->createPresentationProxy(); - shared_ptr credential = - ndk::SharedRefBase::make(hwProxyFactory_, hwProxy, credentialData); + shared_ptr credential = ndk::SharedRefBase::make( + hwProxyFactory_, credentialData, nullptr /* session */); auto ret = credential->initialize(); if (ret != IIdentityCredentialStore::STATUS_OK) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( @@ -73,4 +73,25 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential( return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus IdentityCredentialStore::createPresentationSession( + CipherSuite cipherSuite, shared_ptr* 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 hwProxy = hwProxyFactory_->createSessionProxy(); + shared_ptr session = + ndk::SharedRefBase::make(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 d35e632984..77b894dbd6 100644 --- a/identity/aidl/default/common/IdentityCredentialStore.h +++ b/identity/aidl/default/common/IdentityCredentialStore.h @@ -47,6 +47,9 @@ class IdentityCredentialStore : public BnIdentityCredentialStore { ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector& credentialData, shared_ptr* outCredential) override; + ndk::ScopedAStatus createPresentationSession( + CipherSuite cipherSuite, shared_ptr* outSession) override; + private: sp hwProxyFactory_; }; diff --git a/identity/aidl/default/common/PresentationSession.cpp b/identity/aidl/default/common/PresentationSession.cpp new file mode 100644 index 0000000000..fbd897281a --- /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 + +#include + +#include +#include + +#include +#include + +#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 id = hwProxy_->getId(); + if (!id) { + LOG(ERROR) << "Error getting id for session"; + return IIdentityCredentialStore::STATUS_FAILED; + } + id_ = id.value(); + + optional> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair(); + if (!ephemeralKeyPriv) { + LOG(ERROR) << "Error getting ephemeral private key for session"; + return IIdentityCredentialStore::STATUS_FAILED; + } + optional> ephemeralKeyPair = + support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value()); + if (!ephemeralKeyPair) { + LOG(ERROR) << "Error creating ephemeral key-pair"; + return IIdentityCredentialStore::STATUS_FAILED; + } + ephemeralKeyPair_ = ephemeralKeyPair.value(); + + optional 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* 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& 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 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& 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& credentialData, shared_ptr* outCredential) { + shared_ptr p = ref(); + shared_ptr credential = + ndk::SharedRefBase::make(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 PresentationSession::getSessionTranscript() { + return sessionTranscript_; +} + +vector 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 0000000000..76ca67b675 --- /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 +#include + +#include + +#include + +#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 hwProxyFactory, + sp 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 getSessionTranscript(); + vector getReaderEphemeralPublicKey(); + + // Methods from IPresentationSession follow. + ndk::ScopedAStatus getEphemeralKeyPair(vector* outKeyPair) override; + ndk::ScopedAStatus getAuthChallenge(int64_t* outChallenge) override; + ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector& publicKey) override; + ndk::ScopedAStatus setSessionTranscript(const vector& sessionTranscript) override; + + ndk::ScopedAStatus getCredential(const vector& credentialData, + shared_ptr* outCredential) override; + + private: + // Set by constructor + sp hwProxyFactory_; + sp hwProxy_; + + // Set by initialize() + uint64_t id_; + vector ephemeralKeyPair_; + uint64_t authChallenge_; + + // Set by setReaderEphemeralPublicKey() + vector readerPublicKey_; + + // Set by setSessionTranscript() + vector 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 a1ed1ef03b..a580444230 100644 --- a/identity/aidl/default/common/SecureHardwareProxy.h +++ b/identity/aidl/default/common/SecureHardwareProxy.h @@ -42,6 +42,7 @@ using ::std::vector; // Forward declare. // class SecureHardwareProvisioningProxy; +class SecureHardwareSessionProxy; class SecureHardwarePresentationProxy; // This is a class used to create proxies. @@ -52,6 +53,7 @@ class SecureHardwareProxyFactory : public RefBase { virtual ~SecureHardwareProxyFactory() {} virtual sp createProvisioningProxy() = 0; + virtual sp createSessionProxy() = 0; virtual sp createPresentationProxy() = 0; }; @@ -64,8 +66,12 @@ class SecureHardwareProvisioningProxy : public RefBase { virtual bool initialize(bool testCredential) = 0; - virtual bool initializeForUpdate(bool testCredential, string docType, - vector encryptedCredentialKeys) = 0; + virtual bool initializeForUpdate(bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) = 0; + + virtual optional getId() = 0; + + virtual bool shutdown() = 0; // Returns public key certificate chain with attestation. // @@ -76,7 +82,7 @@ class SecureHardwareProvisioningProxy : public RefBase { virtual optional> createCredentialKey(const vector& challenge, const vector& applicationId) = 0; - virtual bool startPersonalization(int accessControlProfileCount, vector entryCounts, + virtual bool startPersonalization(int accessControlProfileCount, const vector& entryCounts, const string& docType, size_t expectedProofOfProvisioningSize) = 0; @@ -98,8 +104,6 @@ class SecureHardwareProvisioningProxy : public RefBase { // Returns encryptedCredentialKeys (80 bytes). virtual optional> finishGetCredentialData(const string& docType) = 0; - - virtual bool shutdown() = 0; }; enum AccessCheckResult { @@ -110,6 +114,30 @@ enum AccessCheckResult { kReaderAuthenticationFailed, }; +// The proxy used for sessions. +// +class SecureHardwareSessionProxy : public RefBase { + public: + SecureHardwareSessionProxy() {} + + virtual ~SecureHardwareSessionProxy() {} + + virtual bool initialize() = 0; + + virtual optional getId() = 0; + + virtual bool shutdown() = 0; + + virtual optional getAuthChallenge() = 0; + + // Returns private key + virtual optional> getEphemeralKeyPair() = 0; + + virtual bool setReaderEphemeralPublicKey(const vector& readerEphemeralPublicKey) = 0; + + virtual bool setSessionTranscript(const vector& sessionTranscript) = 0; +}; + // The proxy used for presentation. // class SecureHardwarePresentationProxy : public RefBase { @@ -117,12 +145,16 @@ class SecureHardwarePresentationProxy : public RefBase { SecureHardwarePresentationProxy() {} virtual ~SecureHardwarePresentationProxy() {} - virtual bool initialize(bool testCredential, string docType, - vector encryptedCredentialKeys) = 0; + virtual bool initialize(uint32_t sessionId, bool testCredential, const string& docType, + const vector& encryptedCredentialKeys) = 0; + + virtual optional getId() = 0; + + virtual bool shutdown() = 0; // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) - virtual optional, vector>> generateSigningKeyPair(string docType, - time_t now) = 0; + virtual optional, vector>> generateSigningKeyPair( + const string& docType, time_t now) = 0; // Returns private key virtual optional> createEphemeralKeyPair() = 0; @@ -174,8 +206,6 @@ class SecureHardwarePresentationProxy : public RefBase { virtual optional> proveOwnership(const string& docType, bool testCredential, const vector& challenge, size_t proofOfOwnershipCborSize) = 0; - - virtual bool shutdown() = 0; }; } // namespace android::hardware::identity diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml index a074250901..cc0ddc7d51 100644 --- a/identity/aidl/default/identity-default.xml +++ b/identity/aidl/default/identity-default.xml @@ -1,7 +1,7 @@ android.hardware.identity - 3 + 4 IIdentityCredentialStore default diff --git a/identity/aidl/default/libeic/EicCommon.h b/identity/aidl/default/libeic/EicCommon.h index 476276ebcf..2a08a35f65 100644 --- a/identity/aidl/default/libeic/EicCommon.h +++ b/identity/aidl/default/libeic/EicCommon.h @@ -21,6 +21,9 @@ #ifndef ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H #define ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H +// KeyMint auth-challenges are 64-bit numbers and 0 typically means unset. +#define EIC_KM_AUTH_CHALLENGE_UNSET 0 + // Feature version 202009: // // CredentialKeys = [ diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h index d4fcf0e1bb..aa26e6202a 100644 --- a/identity/aidl/default/libeic/EicOps.h +++ b/identity/aidl/default/libeic/EicOps.h @@ -141,6 +141,10 @@ void* eicMemCpy(void* dest, const void* src, size_t n); // String length, see strlen(3). size_t eicStrLen(const char* s); +// Locate a substring, see memmem(3) +void* eicMemMem(const uint8_t* haystack, size_t haystackLen, const uint8_t* needle, + size_t needleLen); + // Memory compare, see CRYPTO_memcmp(3SSL) // // It takes an amount of time dependent on len, but independent of the contents of the @@ -151,6 +155,12 @@ int eicCryptoMemCmp(const void* s1, const void* s2, size_t n); // Random number generation. bool eicOpsRandom(uint8_t* buf, size_t numBytes); +// Creates a new non-zero identifier in |id|. +// +// Is guaranteed to be non-zero and different than what is already in |id|. +// +bool eicNextId(uint32_t* id); + // If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes). // // Otherwise returns all zeroes (16 bytes). @@ -295,6 +305,8 @@ bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t int verificationTokenSecurityLevel, const uint8_t* verificationTokenMac, size_t verificationTokenMacSize); +// Also see eicOpsLookupActiveSessionFromId() defined in EicSession.h + #ifdef __cplusplus } #endif diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c index 0d03ae9620..104a559697 100644 --- a/identity/aidl/default/libeic/EicPresentation.c +++ b/identity/aidl/default/libeic/EicPresentation.c @@ -16,11 +16,17 @@ #include "EicPresentation.h" #include "EicCommon.h" +#include "EicSession.h" #include -bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, - size_t docTypeLength, const uint8_t* encryptedCredentialKeys, +// Global used for assigning ids for presentation objects. +// +static uint32_t gPresentationLastIdAssigned = 0; + +bool eicPresentationInit(EicPresentation* ctx, uint32_t sessionId, bool testCredential, + const char* docType, size_t docTypeLength, + const uint8_t* encryptedCredentialKeys, size_t encryptedCredentialKeysSize) { uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101]; bool expectPopSha256 = false; @@ -39,6 +45,13 @@ bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* } eicMemSet(ctx, '\0', sizeof(EicPresentation)); + ctx->sessionId = sessionId; + + if (!eicNextId(&gPresentationLastIdAssigned)) { + eicDebug("Error getting id for object"); + return false; + } + ctx->id = gPresentationLastIdAssigned; if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys, encryptedCredentialKeysSize, @@ -86,6 +99,23 @@ bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* if (expectPopSha256) { eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54, EIC_SHA256_DIGEST_SIZE); } + + eicDebug("Initialized presentation with id %" PRIu32, ctx->id); + return true; +} + +bool eicPresentationShutdown(EicPresentation* ctx) { + if (ctx->id == 0) { + eicDebug("Trying to shut down presentation with id 0"); + return false; + } + eicDebug("Shut down presentation with id %" PRIu32, ctx->id); + eicMemSet(ctx, '\0', sizeof(EicPresentation)); + return true; +} + +bool eicPresentationGetId(EicPresentation* ctx, uint32_t* outId) { + *outId = ctx->id; return true; } @@ -174,7 +204,7 @@ bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChal eicDebug("Failed generating random challenge"); return false; } - } while (ctx->authChallenge == 0); + } while (ctx->authChallenge == EIC_KM_AUTH_CHALLENGE_UNSET); eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge); *authChallenge = ctx->authChallenge; return true; @@ -190,6 +220,24 @@ bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* int coseSignAlg, const uint8_t* readerSignatureOfToBeSigned, size_t readerSignatureOfToBeSignedSize) { + if (ctx->sessionId != 0) { + EicSession* session = eicSessionGetForId(ctx->sessionId); + if (session == NULL) { + eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId); + return false; + } + EicSha256Ctx sha256; + uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE]; + eicOpsSha256Init(&sha256); + eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize); + eicOpsSha256Final(&sha256, sessionTranscriptSha256); + if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256, + EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SessionTranscript mismatch"); + return false; + } + } + if (ctx->readerPublicKeySize == 0) { eicDebug("No public key for reader"); return false; @@ -330,6 +378,20 @@ bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509 return true; } +static bool getChallenge(EicPresentation* ctx, uint64_t* outAuthChallenge) { + // Use authChallenge from session if applicable. + *outAuthChallenge = ctx->authChallenge; + if (ctx->sessionId != 0) { + EicSession* session = eicSessionGetForId(ctx->sessionId); + if (session == NULL) { + eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId); + return false; + } + *outAuthChallenge = session->authChallenge; + } + return true; +} + bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac, size_t macSize, @@ -338,14 +400,19 @@ bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint6 int verificationTokenSecurityLevel, const uint8_t* verificationTokenMac, size_t verificationTokenMacSize) { + uint64_t authChallenge; + if (!getChallenge(ctx, &authChallenge)) { + return false; + } + // It doesn't make sense to accept any tokens if eicPresentationCreateAuthChallenge() // was never called. - if (ctx->authChallenge == 0) { - eicDebug("Trying validate tokens when no auth-challenge was previously generated"); + if (authChallenge == EIC_KM_AUTH_CHALLENGE_UNSET) { + eicDebug("Trying to validate tokens when no auth-challenge was previously generated"); return false; } // At least the verification-token must have the same challenge as what was generated. - if (verificationTokenChallenge != ctx->authChallenge) { + if (verificationTokenChallenge != authChallenge) { eicDebug("Challenge in verification token does not match the challenge " "previously generated"); return false; @@ -354,6 +421,7 @@ bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint6 challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac, macSize, verificationTokenChallenge, verificationTokenTimestamp, verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) { + eicDebug("Error validating authToken"); return false; } ctx->authTokenChallenge = challenge; @@ -377,11 +445,16 @@ static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, // Only ACP with auth-on-every-presentation - those with timeout == 0 - need the // challenge to match... if (timeoutMillis == 0) { - if (ctx->authTokenChallenge != ctx->authChallenge) { + uint64_t authChallenge; + if (!getChallenge(ctx, &authChallenge)) { + return false; + } + + if (ctx->authTokenChallenge != authChallenge) { eicDebug("Challenge in authToken (%" PRIu64 ") doesn't match the challenge " "that was created (%" PRIu64 ") for this session", - ctx->authTokenChallenge, ctx->authChallenge); + ctx->authTokenChallenge, authChallenge); return false; } } @@ -490,6 +563,25 @@ bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTrans const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength, unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) { + if (ctx->sessionId != 0) { + EicSession* session = eicSessionGetForId(ctx->sessionId); + if (session == NULL) { + eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId); + return false; + } + EicSha256Ctx sha256; + uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE]; + eicOpsSha256Init(&sha256); + eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize); + eicOpsSha256Final(&sha256, sessionTranscriptSha256); + if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256, + EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SessionTranscript mismatch"); + return false; + } + readerEphemeralPublicKey = session->readerEphemeralPublicKey; + } + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType, docTypeLength, signingKeyPriv)) { diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h index 6f7f432960..a031890e58 100644 --- a/identity/aidl/default/libeic/EicPresentation.h +++ b/identity/aidl/default/libeic/EicPresentation.h @@ -30,7 +30,13 @@ extern "C" { // The maximum size we support for public keys in reader certificates. #define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65 +// Constant used to convey that no session is associated with a presentation. +#define EIC_PRESENTATION_ID_UNSET 0 + typedef struct { + // A non-zero number unique for this EicPresentation instance + uint32_t id; + int featureLevel; uint8_t storageKey[EIC_AES_128_KEY_SIZE]; @@ -38,6 +44,10 @@ typedef struct { uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + // If non-zero (not EIC_PRESENTATION_ID_UNSET), the id of the EicSession object this + // presentation object is associated with. + uint32_t sessionId; + // The challenge generated with eicPresentationCreateAuthChallenge() uint64_t authChallenge; @@ -93,10 +103,18 @@ typedef struct { EicCbor cbor; } EicPresentation; -bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, - size_t docTypeLength, const uint8_t* encryptedCredentialKeys, +// If sessionId is zero (EIC_PRESENTATION_ID_UNSET), the presentation object is not associated +// with a session object. Otherwise it's the id of the session object. +// +bool eicPresentationInit(EicPresentation* ctx, uint32_t sessionId, bool testCredential, + const char* docType, size_t docTypeLength, + const uint8_t* encryptedCredentialKeys, size_t encryptedCredentialKeysSize); +bool eicPresentationShutdown(EicPresentation* ctx); + +bool eicPresentationGetId(EicPresentation* ctx, uint32_t* outId); + bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, size_t docTypeLength, time_t now, uint8_t* publicKeyCert, size_t* publicKeyCertSize, diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c index c9df4fd74f..a241b71b50 100644 --- a/identity/aidl/default/libeic/EicProvisioning.c +++ b/identity/aidl/default/libeic/EicProvisioning.c @@ -17,8 +17,21 @@ #include "EicProvisioning.h" #include "EicCommon.h" +#include + +// Global used for assigning ids for provisioning objects. +// +static uint32_t gProvisioningLastIdAssigned = 0; + bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) { eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + + if (!eicNextId(&gProvisioningLastIdAssigned)) { + eicDebug("Error getting id for object"); + return false; + } + ctx->id = gProvisioningLastIdAssigned; + ctx->testCredential = testCredential; if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) { return false; @@ -47,6 +60,13 @@ bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, con } eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + + if (!eicNextId(&gProvisioningLastIdAssigned)) { + eicDebug("Error getting id for object"); + return false; + } + ctx->id = gProvisioningLastIdAssigned; + ctx->testCredential = testCredential; if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys, @@ -96,6 +116,21 @@ bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, con return true; } +bool eicProvisioningShutdown(EicProvisioning* ctx) { + if (ctx->id == 0) { + eicDebug("Trying to shut down provsioning with id 0"); + return false; + } + eicDebug("Shut down provsioning with id %" PRIu32, ctx->id); + eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + return true; +} + +bool eicProvisioningGetId(EicProvisioning* ctx, uint32_t* outId) { + *outId = ctx->id; + return true; +} + bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize, const uint8_t* applicationId, size_t applicationIdSize, uint8_t* publicKeyCert, diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h index 92f1e4a2a0..d94f8f18c2 100644 --- a/identity/aidl/default/libeic/EicProvisioning.h +++ b/identity/aidl/default/libeic/EicProvisioning.h @@ -31,6 +31,9 @@ extern "C" { #define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32 typedef struct { + // A non-zero number unique for this EicProvisioning instance + uint32_t id; + // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate() uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; @@ -68,6 +71,10 @@ bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, con size_t docTypeLength, const uint8_t* encryptedCredentialKeys, size_t encryptedCredentialKeysSize); +bool eicProvisioningShutdown(EicProvisioning* ctx); + +bool eicProvisioningGetId(EicProvisioning* ctx, uint32_t* outId); + bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize, const uint8_t* applicationId, size_t applicationIdSize, uint8_t* publicKeyCert, diff --git a/identity/aidl/default/libeic/EicSession.c b/identity/aidl/default/libeic/EicSession.c new file mode 100644 index 0000000000..d0c7a0d77e --- /dev/null +++ b/identity/aidl/default/libeic/EicSession.c @@ -0,0 +1,120 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "EicCommon.h" +#include "EicSession.h" + +// Global used for assigning ids for session objects. +// +static uint32_t gSessionLastIdAssigned = 0; + +// The current session object or NULL if never initialized or if it has been shut down. +// +static EicSession* gSessionCurrent = NULL; + +EicSession* eicSessionGetForId(uint32_t sessionId) { + if (gSessionCurrent != NULL && gSessionCurrent->id == sessionId) { + return gSessionCurrent; + } + return NULL; +} + +bool eicSessionInit(EicSession* ctx) { + eicMemSet(ctx, '\0', sizeof(EicSession)); + + if (!eicNextId(&gSessionLastIdAssigned)) { + eicDebug("Error getting id for object"); + return false; + } + ctx->id = gSessionLastIdAssigned; + + do { + if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(ctx->authChallenge))) { + eicDebug("Failed generating random challenge"); + return false; + } + } while (ctx->authChallenge == EIC_KM_AUTH_CHALLENGE_UNSET); + + if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ctx->ephemeralPublicKey)) { + eicDebug("Error creating ephemeral key-pair"); + return false; + } + + gSessionCurrent = ctx; + eicDebug("Initialized session with id %" PRIu32, ctx->id); + return true; +} + +bool eicSessionShutdown(EicSession* ctx) { + if (ctx->id == 0) { + eicDebug("Trying to shut down session with id 0"); + return false; + } + eicDebug("Shut down session with id %" PRIu32, ctx->id); + eicMemSet(ctx, '\0', sizeof(EicSession)); + gSessionCurrent = NULL; + return true; +} + +bool eicSessionGetId(EicSession* ctx, uint32_t* outId) { + *outId = ctx->id; + return true; +} + +bool eicSessionGetAuthChallenge(EicSession* ctx, uint64_t* outAuthChallenge) { + *outAuthChallenge = ctx->authChallenge; + return true; +} + +bool eicSessionGetEphemeralKeyPair(EicSession* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) { + eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE); + return true; +} + +bool eicSessionSetReaderEphemeralPublicKey( + EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]) { + eicMemCpy(ctx->readerEphemeralPublicKey, readerEphemeralPublicKey, EIC_P256_PUB_KEY_SIZE); + return true; +} + +bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize) { + // Only accept the SessionTranscript if X and Y from the ephemeral key + // we created is somewhere in SessionTranscript... + // + if (eicMemMem(sessionTranscript, sessionTranscriptSize, ctx->ephemeralPublicKey, + EIC_P256_PUB_KEY_SIZE / 2) == NULL) { + eicDebug("Error finding X from ephemeralPublicKey in sessionTranscript"); + return false; + } + if (eicMemMem(sessionTranscript, sessionTranscriptSize, + ctx->ephemeralPublicKey + EIC_P256_PUB_KEY_SIZE / 2, + EIC_P256_PUB_KEY_SIZE / 2) == NULL) { + eicDebug("Error finding Y from ephemeralPublicKey in sessionTranscript"); + return false; + } + + // To save space we only store the SHA-256 of SessionTranscript + // + EicSha256Ctx shaCtx; + eicOpsSha256Init(&shaCtx); + eicOpsSha256Update(&shaCtx, sessionTranscript, sessionTranscriptSize); + eicOpsSha256Final(&shaCtx, ctx->sessionTranscriptSha256); + return true; +} diff --git a/identity/aidl/default/libeic/EicSession.h b/identity/aidl/default/libeic/EicSession.h new file mode 100644 index 0000000000..0303dae1c3 --- /dev/null +++ b/identity/aidl/default/libeic/EicSession.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_SESSION_H +#define ANDROID_HARDWARE_IDENTITY_EIC_SESSION_H + +#include "EicOps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // A non-zero number unique for this EicSession instance + uint32_t id; + + // The challenge generated at construction time by eicSessionInit(). + uint64_t authChallenge; + + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + + uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + + uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE]; + +} EicSession; + +bool eicSessionInit(EicSession* ctx); + +bool eicSessionShutdown(EicSession* ctx); + +bool eicSessionGetId(EicSession* ctx, uint32_t* outId); + +bool eicSessionGetAuthChallenge(EicSession* ctx, uint64_t* outAuthChallenge); + +bool eicSessionGetEphemeralKeyPair(EicSession* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]); + +bool eicSessionSetReaderEphemeralPublicKey( + EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]); + +bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize); + +// Looks up an active session with the given id. +// +// Returns NULL if no active session with the given id is found. +// +EicSession* eicSessionGetForId(uint32_t sessionId); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h index 20c889660f..d89fc9a0e3 100644 --- a/identity/aidl/default/libeic/libeic.h +++ b/identity/aidl/default/libeic/libeic.h @@ -27,10 +27,11 @@ extern "C" { */ #define EIC_INSIDE_LIBEIC_H #include "EicCbor.h" +#include "EicCommon.h" #include "EicOps.h" #include "EicPresentation.h" #include "EicProvisioning.h" -#include "EicCommon.h" +#include "EicSession.h" #undef EIC_INSIDE_LIBEIC_H #ifdef __cplusplus -- cgit v1.2.3