Merge "identity: Add multi-document presentation support."

This commit is contained in:
David Zeuthen
2022-01-14 21:07:03 +00:00
committed by Gerrit Code Review
39 changed files with 1777 additions and 169 deletions

View File

@@ -292,7 +292,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.identity</name>
<version>1-3</version>
<version>1-4</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
@@ -36,6 +37,7 @@ interface IIdentityCredentialStore {
android.hardware.identity.HardwareInformation getHardwareInformation();
android.hardware.identity.IWritableIdentityCredential createCredential(in @utf8InCpp String docType, in boolean testCredential);
android.hardware.identity.IIdentityCredential getCredential(in android.hardware.identity.CipherSuite cipherSuite, in byte[] credentialData);
android.hardware.identity.IPresentationSession createPresentationSession(in android.hardware.identity.CipherSuite cipherSuite);
const int STATUS_OK = 0;
const int STATUS_FAILED = 1;
const int STATUS_CIPHER_SUITE_NOT_SUPPORTED = 2;

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.identity;
@VintfStability
interface IPresentationSession {
byte[] getEphemeralKeyPair();
long getAuthChallenge();
void setReaderEphemeralPublicKey(in byte[] publicKey);
void setSessionTranscript(in byte[] sessionTranscript);
android.hardware.identity.IIdentityCredential getCredential(in byte[] credentialData);
}

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////

View File

@@ -17,9 +17,9 @@
package android.hardware.identity;
import android.hardware.identity.Certificate;
import android.hardware.identity.IWritableIdentityCredential;
import android.hardware.identity.RequestNamespace;
import android.hardware.identity.SecureAccessControlProfile;
import android.hardware.identity.IWritableIdentityCredential;
import android.hardware.keymaster.HardwareAuthToken;
import android.hardware.keymaster.VerificationToken;
@@ -44,6 +44,9 @@ interface IIdentityCredential {
* This method was deprecated in API version 3 because there's no challenge so freshness
* can't be checked. Use deleteCredentalWithChallenge() instead.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @return a COSE_Sign1 signature described above
* @deprecated use deleteCredentalWithChallenge() instead.
*/
@@ -60,6 +63,9 @@ interface IIdentityCredential {
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* will be returned.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @return the private key, in DER format as specified in RFC 5915.
*/
byte[] createEphemeralKeyPair();
@@ -70,6 +76,9 @@ interface IIdentityCredential {
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* will be returned.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @param publicKey contains the reader's ephemeral public key, in uncompressed
* form (e.g. 0x04 || X || Y).
*/
@@ -83,6 +92,9 @@ interface IIdentityCredential {
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* will be returned. If user authentication is not needed, this method may not be called.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @return challenge, a non-zero number.
*/
long createAuthChallenge();
@@ -371,6 +383,9 @@ interface IIdentityCredential {
* This CBOR enables an issuer to determine the exact state of the credential it
* returns issuer-signed data for.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType)
* where signingKey is an EC private key in uncompressed form. That is, the returned
* blob is an encrypted copy of the newly-generated private signing key.
@@ -420,6 +435,9 @@ interface IIdentityCredential {
*
* This method was introduced in API version 3.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
* and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
* @return a COSE_Sign1 signature described above.
@@ -442,6 +460,9 @@ interface IIdentityCredential {
*
* This method was introduced in API version 3.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
* and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
* @return a COSE_Sign1 signature described above.
@@ -456,6 +477,9 @@ interface IIdentityCredential {
*
* This method was introduced in API version 3.
*
* If the method is called on an instance obtained via IPresentationSession.getCredential(),
* STATUS_FAILED must be returned.
*
* @return an IWritableIdentityCredential
*/
IWritableIdentityCredential updateCredential();

View File

@@ -16,10 +16,11 @@
package android.hardware.identity;
import android.hardware.identity.IIdentityCredential;
import android.hardware.identity.IWritableIdentityCredential;
import android.hardware.identity.HardwareInformation;
import android.hardware.identity.CipherSuite;
import android.hardware.identity.HardwareInformation;
import android.hardware.identity.IIdentityCredential;
import android.hardware.identity.IPresentationSession;
import android.hardware.identity.IWritableIdentityCredential;
/**
* IIdentityCredentialStore provides an interface to a secure store for user identity documents.
@@ -105,7 +106,7 @@ import android.hardware.identity.CipherSuite;
* STATUS_* integers defined in this interface. Each method states which status can be returned
* and under which circumstances.
*
* The API described here is API version 3 which corresponds to feature version 202101
* The API described here is API version 4 which corresponds to feature version 202201
* of the android.security.identity Framework API. An XML file declaring the feature
* android.hardware.identity_credential (or android.hardware.identity_credential.direct_access
* if implementing the Direct Access HAL) should be included declaring this feature version.
@@ -241,4 +242,25 @@ interface IIdentityCredentialStore {
* @return an IIdentityCredential interface that provides operations on the Credential.
*/
IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);
/**
* createPresentationSession creates IPresentationSession interface which can be used to
* present one or more credentials to a remote verifier device.
*
* The cipher suite used to communicate with the remote verifier must be specified. Currently
* only a single cipher-suite is supported. Support for other cipher suites may be added in a
* future version of this HAL. If the requested cipher suite is not support the call fails
* with STATUS_CIPHER_SUITE_NOT_SUPPORTED.
*
* In this version of the HAL, implementations are only required to support a single session
* being active. In a future version, implementations may be required to support multiple
* presentation sessions being active at the same time.
*
* This method was introduced in API version 4.
*
* @param cipherSuite The cipher suite to use.
*
* @return an IPresentationSession interface.
*/
IPresentationSession createPresentationSession(in CipherSuite cipherSuite);
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.identity;
import android.hardware.identity.CipherSuite;
import android.hardware.identity.IIdentityCredential;
/**
* An interface to present multiple credentials in the same session.
*
* This interface was introduced in API version 4.
*
*/
@VintfStability
interface IPresentationSession {
/**
* Gets the ephemeral EC key pair to be used in establishing a secure session with a reader.
* This method returns the private key so the caller can perform an ECDH key agreement operation
* with the reader. The reason for generating the key pair in the secure environment is so that
* the secure environment knows what public key to expect to find in the session transcript
* when presenting credentials.
*
* The generated key matches the selected cipher suite of the presentation session (e.g. EC
* key using the P-256 curve).
*
* @return the private key, in DER format as specified in RFC 5915.
*/
byte[] getEphemeralKeyPair();
/**
* Gets the challenge value to be used for proving successful user authentication. This
* is to be included in the authToken passed to the IIdentityCredential.startRetrieval()
* method and the verificationToken passed to the IIdentityCredential.setVerificationToken()
* method.
*
* @return challenge, a non-zero number.
*/
long getAuthChallenge();
/**
* Sets the public part of the reader's ephemeral key pair to be used to complete
* an ECDH key agreement for the session.
*
* The curve of the key must match the curve for the key returned by getEphemeralKeyPair().
*
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* must be returned.
*
* @param publicKey contains the reader's ephemeral public key, in uncompressed
* form (e.g. 0x04 || X || Y).
*/
void setReaderEphemeralPublicKey(in byte[] publicKey);
/**
* Sets the session transcript for the session.
*
* This can be empty but if it's non-empty it must be valid CBOR.
*
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* must be returned.
*
* @param sessionTrancsript the session transcript.
*/
void setSessionTranscript(in byte[] sessionTranscript);
/**
* getCredential() retrieves an IIdentityCredential interface for presentation in the
* current presentation session.
*
* On the returned instance only the methods startRetrieval(), startRetrieveEntryValue(),
* retrieveEntryValue(), finishRetrieval(), setRequestedNamespaces(), setVerificationToken()
* may be called. Other methods will fail with STATUS_FAILED.
*
* The implementation is expected to get the session transcript, ephemeral key, reader
* ephemeral key, and auth challenge from this instance.
*
* @param credentialData is a CBOR-encoded structure containing metadata about the credential
* and an encrypted byte array that contains data used to secure the credential. See the
* return argument of the same name in IWritableIdentityCredential.finishAddingEntries().
*
* Note that the format of credentialData may depend on the feature version.
* Implementations must support credentialData created by an earlier feature version.
*
* @return an IIdentityCredential interface that provides operations on the Credential.
*/
IIdentityCredential getCredential(in byte[] credentialData);
}

View File

@@ -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",
],

View File

@@ -20,9 +20,13 @@
#include <tuple>
#include <vector>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <string.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <string.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
@@ -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

View File

@@ -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);

View File

@@ -23,6 +23,7 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <string.h>
#include <map>
#include <openssl/sha.h>
@@ -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<uint32_t> id = getId();
if (!id) {
LOG(WARNING) << "Error getting id";
return false;
}
id_ = id.value();
return true;
}
bool FakeSecureHardwareProvisioningProxy::initializeForUpdate(
bool testCredential, string docType, vector<uint8_t> encryptedCredentialKeys) {
return eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(),
docType.size(),
encryptedCredentialKeys.data(),
encryptedCredentialKeys.size());
bool testCredential, const string& docType,
const vector<uint8_t>& 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<uint32_t> id = getId();
if (!id) {
LOG(WARNING) << "Error getting id";
return false;
}
id_ = id.value();
return true;
}
optional<uint32_t> 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<uint32_t> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
const vector<uint8_t>& challenge, const vector<uint8_t>& 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<uint8_t> pubKeyCert(publicKeyCertSize);
memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
@@ -91,8 +164,11 @@ optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialK
}
bool FakeSecureHardwareProvisioningProxy::startPersonalization(
int accessControlProfileCount, vector<int> entryCounts, const string& docType,
int accessControlProfileCount, const vector<int>& 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile(
int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
uint64_t timeoutMillis, uint64_t secureUserId) {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlP
bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& accessControlProfileIds,
const string& nameSpace, const string& name,
uint64_t entrySize) {
if (!validateId(__func__)) {
return false;
}
uint8_t scratchSpace[512];
vector<uint8_t> uint8AccessControlProfileIds;
for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
@@ -138,6 +222,10 @@ bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& acces
optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue(
const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name,
const vector<uint8_t>& content) {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> eicEncryptedContent;
uint8_t scratchSpace[512];
vector<uint8_t> uint8AccessControlProfileIds;
@@ -150,16 +238,20 @@ optional<vector<uint8_t>> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) {
return {};
return std::nullopt;
}
return signatureOfToBeSigned;
}
@@ -167,11 +259,15 @@ optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntri
// Returns encryptedCredentialKeys.
optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
const string& docType) {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredenti
// ----------------------------------------------------------------------
FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {}
// The singleton EicSession object used everywhere.
//
EicSession FakeSecureHardwareSessionProxy::ctx_;
FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {}
FakeSecureHardwareSessionProxy::~FakeSecureHardwareSessionProxy() {
if (id_ != 0) {
shutdown();
}
}
bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType,
vector<uint8_t> encryptedCredentialKeys) {
LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): "
<< sizeof(EicPresentation);
return eicPresentationInit(&ctx_, testCredential, docType.c_str(), docType.size(),
encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
bool FakeSecureHardwareSessionProxy::initialize() {
if (id_ != 0) {
LOG(WARNING) << "Proxy is already initialized";
return false;
}
bool initialized = eicSessionInit(&ctx_);
if (!initialized) {
return false;
}
optional<uint32_t> id = getId();
if (!id) {
LOG(WARNING) << "Error getting id";
return false;
}
id_ = id.value();
return true;
}
optional<uint32_t> 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<uint32_t> 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;
}
optional<uint64_t> FakeSecureHardwareSessionProxy::getAuthChallenge() {
if (!validateId(__func__)) {
return std::nullopt;
}
uint64_t authChallenge;
if (!eicSessionGetAuthChallenge(&ctx_, &authChallenge)) {
return std::nullopt;
}
return authChallenge;
}
optional<vector<uint8_t>> FakeSecureHardwareSessionProxy::getEphemeralKeyPair() {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
if (!eicSessionGetEphemeralKeyPair(&ctx_, priv.data())) {
return std::nullopt;
}
return priv;
}
bool FakeSecureHardwareSessionProxy::setReaderEphemeralPublicKey(
const vector<uint8_t>& readerEphemeralPublicKey) {
if (!validateId(__func__)) {
return false;
}
return eicSessionSetReaderEphemeralPublicKey(&ctx_, readerEphemeralPublicKey.data());
}
bool FakeSecureHardwareSessionProxy::setSessionTranscript(
const vector<uint8_t>& 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<uint8_t>& 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<uint32_t> id = getId();
if (!id) {
LOG(WARNING) << "Error getting id";
return false;
}
id_ = id.value();
return true;
}
optional<uint32_t> 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<uint32_t> 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<pair<vector<uint8_t>, vector<uint8_t>>>
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<uint8_t> 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<uint8_t> cert;
@@ -213,33 +488,44 @@ FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time
// Returns private key
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
return {};
return std::nullopt;
}
return priv;
}
optional<uint64_t> 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<uint8_t>& certX509) {
if (!validateId(__func__)) {
return false;
}
return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
}
bool FakeSecureHardwarePresentationProxy::validateRequestMessage(
const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage,
int coseSignAlg, const vector<uint8_t>& 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<uint8_t>& mac,
uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
int verificationTokenSecurityLevel, const vector<uint8_t>& 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<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile(
int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& 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<bool> 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<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
const vector<uint8_t>& 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<int32_t>& accessControlProfileIds) {
if (!validateId(__func__)) {
return AccessCheckResult::kFailed;
}
uint8_t scratchSpace[512];
vector<uint8_t> uint8AccessControlProfileIds;
for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
@@ -324,6 +630,10 @@ AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue(
const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
const vector<int32_t>& accessControlProfileIds) {
if (!validateId(__func__)) {
return std::nullopt;
}
uint8_t scratchSpace[512];
vector<uint8_t> uint8AccessControlProfileIds;
for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
@@ -337,16 +647,20 @@ optional<vector<uint8_t>> 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<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval()
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
const string& docType, const vector<uint8_t>& challenge, bool includeChallenge,
size_t proofOfDeletionCborSize) {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::proveOwnership(
const string& docType, bool testCredential, const vector<uint8_t>& challenge,
size_t proofOfOwnershipCborSize) {
if (!validateId(__func__)) {
return std::nullopt;
}
vector<uint8_t> 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;
}

View File

@@ -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<uint8_t> encryptedCredentialKeys) override;
bool initializeForUpdate(bool testCredential, const string& docType,
const vector<uint8_t>& encryptedCredentialKeys) override;
bool shutdown() override;
optional<uint32_t> getId() override;
// Returns public key certificate.
optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
const vector<uint8_t>& applicationId) override;
bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
bool startPersonalization(int accessControlProfileCount, const vector<int>& entryCounts,
const string& docType,
size_t expectedProofOfProvisioningSize) override;
@@ -67,21 +69,81 @@ class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningPro
optional<vector<uint8_t>> 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<uint32_t> getId() override;
optional<uint64_t> getAuthChallenge() override;
// Returns private key
optional<vector<uint8_t>> getEphemeralKeyPair() override;
bool setReaderEphemeralPublicKey(const vector<uint8_t>& readerEphemeralPublicKey) override;
bool setSessionTranscript(const vector<uint8_t>& 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<uint8_t> encryptedCredentialKeys) override;
bool initialize(uint32_t sessionId, bool testCredential, const string& docType,
const vector<uint8_t>& encryptedCredentialKeys) override;
bool shutdown() override;
optional<uint32_t> getId() override;
// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(const string& docType,
time_t now) override;
// Returns private key
@@ -133,10 +195,23 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
const vector<uint8_t>& 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<SecureHardwareSessionProxy> createSessionProxy() override {
return new FakeSecureHardwareSessionProxy();
}
sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
return new FakeSecureHardwarePresentationProxy();
}

View File

@@ -14,5 +14,5 @@
limitations under the License.
-->
<permissions>
<feature name="android.hardware.identity_credential" version="202101" />
<feature name="android.hardware.identity_credential" version="202201" />
</permissions>

View File

@@ -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<uint8_t>* outProofOfDeletionSignature) {
return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
@@ -93,6 +117,14 @@ ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
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::deleteCredentialCommon(
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::proveOwnership(
}
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::createEphemeralKeyPair(vector<uint8_t>* o
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 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
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 @@ 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<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 @@ void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileM
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::startRetrieveEntryValue(
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::retrieveEntryValue(const vector<uint8_t>&
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 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* 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<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::finishRetrieval(vector<uint8_t>* outMac,
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::generateSigningKeyPair(
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(

View File

@@ -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 @@ using ::std::vector;
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 @@ class IdentityCredential : public BnIdentityCredential {
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 @@ class IdentityCredential : public BnIdentityCredential {
bool testCredential_;
vector<uint8_t> encryptedCredentialKeys_;
// Set by ensureHwProxy()
sp<SecureHardwarePresentationProxy> hwProxy_;
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;

View File

@@ -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<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 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential(
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

View File

@@ -47,6 +47,9 @@ class IdentityCredentialStore : public BnIdentityCredentialStore {
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_;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
virtual sp<SecureHardwareSessionProxy> createSessionProxy() = 0;
virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
};
@@ -64,8 +66,12 @@ class SecureHardwareProvisioningProxy : public RefBase {
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 @@ class SecureHardwareProvisioningProxy : public RefBase {
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 @@ class SecureHardwareProvisioningProxy : public RefBase {
// Returns encryptedCredentialKeys (80 bytes).
virtual optional<vector<uint8_t>> 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<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 @@ class SecureHardwarePresentationProxy : public RefBase {
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 @@ class SecureHardwarePresentationProxy : public RefBase {
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

View File

@@ -1,7 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.identity</name>
<version>3</version>
<version>4</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>

View File

@@ -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 = [

View File

@@ -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

View File

@@ -16,11 +16,17 @@
#include "EicPresentation.h"
#include "EicCommon.h"
#include "EicSession.h"
#include <inttypes.h>
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)) {

View File

@@ -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,

View File

@@ -17,8 +17,21 @@
#include "EicProvisioning.h"
#include "EicCommon.h"
#include <inttypes.h>
// 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,

View File

@@ -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,

View File

@@ -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 <inttypes.h>
#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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -28,6 +28,7 @@ cc_test {
"EndToEndTests.cpp",
"TestCredentialTests.cpp",
"AuthenticationKeyTests.cpp",
"PresentationSessionTests.cpp",
],
shared_libs: [
"libbinder",
@@ -40,9 +41,9 @@ cc_test {
"libpuresoftkeymasterdevice",
"android.hardware.keymaster@4.0",
"android.hardware.identity-support-lib",
"android.hardware.identity-V3-cpp",
"android.hardware.keymaster-V3-cpp",
"android.hardware.keymaster-V3-ndk",
"android.hardware.identity-V4-cpp",
"android.hardware.keymaster-V4-cpp",
"android.hardware.keymaster-V4-ndk",
"libkeymaster4support",
"libkeymaster4_1support",
],

View File

@@ -0,0 +1,197 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "PresentationSessionTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class PresentationSessionTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
void provisionData();
void provisionSingleDocument(const string& docType, vector<uint8_t>* outCredentialData,
vector<uint8_t>* outCredentialPubKey);
// Set by provisionData
vector<uint8_t> credential1Data_;
vector<uint8_t> credential1PubKey_;
vector<uint8_t> credential2Data_;
vector<uint8_t> credential2PubKey_;
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
void PresentationSessionTests::provisionData() {
provisionSingleDocument("org.iso.18013-5.2019.mdl", &credential1Data_, &credential1PubKey_);
provisionSingleDocument("org.blah.OtherhDocTypeXX", &credential2Data_, &credential2PubKey_);
}
void PresentationSessionTests::provisionSingleDocument(const string& docType,
vector<uint8_t>* outCredentialData,
vector<uint8_t>* outCredentialPubKey) {
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId;
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
*outCredentialPubKey = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 106;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Some Data", 1).isOk());
vector<uint8_t> encryptedData;
ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(outCredentialData, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
// This checks that any methods called on an IIdentityCredential obtained via a session
// returns STATUS_FAILED except for startRetrieval(), startRetrieveEntryValue(),
// retrieveEntryValue(), finishRetrieval(), setRequestedNamespaces(), setVerificationToken()
//
TEST_P(PresentationSessionTests, returnsFailureOnUnsupportedMethods) {
if (halApiVersion_ < 4) {
GTEST_SKIP() << "Need HAL API version 4, have " << halApiVersion_;
}
provisionData();
sp<IPresentationSession> session;
ASSERT_TRUE(credentialStore_
->createPresentationSession(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
&session)
.isOk());
sp<IIdentityCredential> credential;
ASSERT_TRUE(session->getCredential(credential1Data_, &credential).isOk());
Status result;
vector<uint8_t> signatureProofOfDeletion;
result = credential->deleteCredential(&signatureProofOfDeletion);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
vector<uint8_t> ephemeralKeyPair;
result = credential->createEphemeralKeyPair(&ephemeralKeyPair);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
result = credential->setReaderEphemeralPublicKey({});
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
int64_t authChallenge;
result = credential->createAuthChallenge(&authChallenge);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
Certificate certificate;
vector<uint8_t> signingKeyBlob;
result = credential->generateSigningKeyPair(&signingKeyBlob, &certificate);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
result = credential->deleteCredentialWithChallenge({}, &signatureProofOfDeletion);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
vector<uint8_t> signatureProofOfOwnership;
result = credential->proveOwnership({}, &signatureProofOfOwnership);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
sp<IWritableIdentityCredential> writableCredential;
result = credential->updateCredential(&writableCredential);
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
}
// TODO: need to add tests to check that the returned IIdentityCredential works
// as intended.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PresentationSessionTests);
INSTANTIATE_TEST_SUITE_P(
Identity, PresentationSessionTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity