diff options
author | Haamed Gheibi <haamed@google.com> | 2022-02-04 13:47:26 -0800 |
---|---|---|
committer | Haamed Gheibi <haamed@google.com> | 2022-02-04 13:55:47 -0800 |
commit | f99b35c293439db0b7436b47b939eb8c7bf21b51 (patch) | |
tree | 6cd9b0719554809447c845616317cca5409b93ae /identity | |
parent | a028272dee9220e6810cbdcfb2328c34f8afe4c2 (diff) | |
parent | 332dead340bb196c6ba3f6978e8fb53966c74bf7 (diff) |
Merge TP1A.220120.003
Change-Id: Ie5eba313ee102e452f5f96942ed2f3a7bb4e8f01
Diffstat (limited to 'identity')
38 files changed, 1773 insertions, 165 deletions
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl index d8a8128c8e..83e1797386 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl index 2685525044..e6ec04e805 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl index f8d5a9e7e6..cd8d56b5bd 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl index 3224e4bf06..5065641109 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl index c6fb3c889e..c912c526ab 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl @@ -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; diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IPresentationSession.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IPresentationSession.aidl new file mode 100644 index 0000000000..705dc292c5 --- /dev/null +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IPresentationSession.aidl @@ -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); +} diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl index 19a29ec719..9a0fa9e9e5 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl index c9c2b9fec6..cec8e0c94d 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl index aaf1e20f1d..05b9ec295f 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl index 695fb3fb26..2003594eb4 100644 --- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl +++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl @@ -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. // /////////////////////////////////////////////////////////////////////////////// diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl index 8ae293b2d3..84d6ed0e8a 100644 --- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl +++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl @@ -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(); diff --git a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl index 638be796c4..86be7f5879 100644 --- a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl +++ b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl @@ -16,10 +16,11 @@ package android.hardware.identity; +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; -import android.hardware.identity.HardwareInformation; -import android.hardware.identity.CipherSuite; /** * 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); } diff --git a/identity/aidl/android/hardware/identity/IPresentationSession.aidl b/identity/aidl/android/hardware/identity/IPresentationSession.aidl new file mode 100644 index 0000000000..b0449f0bba --- /dev/null +++ b/identity/aidl/android/hardware/identity/IPresentationSession.aidl @@ -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); +} diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp index 3de8d30148..ca24afa6cc 100644 --- a/identity/aidl/default/Android.bp +++ b/identity/aidl/default/Android.bp @@ -13,6 +13,7 @@ cc_library_static { srcs: [ "common/IdentityCredential.cpp", "common/IdentityCredentialStore.cpp", + "common/PresentationSession.cpp", "common/WritableIdentityCredential.cpp", ], export_include_dirs: [ @@ -39,8 +40,8 @@ cc_library_static { "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", - "android.hardware.identity-V3-ndk", - "android.hardware.keymaster-V3-ndk", + "android.hardware.identity-V4-ndk", + "android.hardware.keymaster-V4-ndk", ], } @@ -49,6 +50,7 @@ cc_library_static { vendor_available: true, srcs: [ "libeic/EicCbor.c", + "libeic/EicSession.c", "libeic/EicPresentation.c", "libeic/EicProvisioning.c", "EicOpsImpl.cc", @@ -100,8 +102,8 @@ cc_binary { "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", - "android.hardware.identity-V3-ndk", - "android.hardware.keymaster-V3-ndk", + "android.hardware.identity-V4-ndk", + "android.hardware.keymaster-V4-ndk", "android.hardware.identity-libeic-hal-common", "android.hardware.identity-libeic-library", ], diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc index 8ec4cc9b58..c98a91ebc3 100644 --- a/identity/aidl/default/EicOpsImpl.cc +++ b/identity/aidl/default/EicOpsImpl.cc @@ -20,9 +20,13 @@ #include <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 diff --git a/identity/aidl/default/EicTests.cpp b/identity/aidl/default/EicTests.cpp index a28080d009..7b69b75acd 100644 --- a/identity/aidl/default/EicTests.cpp +++ b/identity/aidl/default/EicTests.cpp @@ -66,7 +66,8 @@ TEST(EicTest, AccessControlIsEnforced) { // Then present data from it... // FakeSecureHardwarePresentationProxy presentationProxy; - ASSERT_TRUE(presentationProxy.initialize(isTestCredential, docType, credData.value())); + ASSERT_TRUE(presentationProxy.initialize(0 /* sessionId */, isTestCredential, docType, + credData.value())); AccessCheckResult res = presentationProxy.startRetrieveEntryValue(nameSpace, name, 1, content.size(), acpIds); ASSERT_EQ(res, AccessCheckResult::kNoAccessControlProfiles); diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp index f0307dc324..91e634c0c3 100644 --- a/identity/aidl/default/FakeSecureHardwareProxy.cpp +++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp @@ -23,6 +23,7 @@ #include <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_; + +FakeSecureHardwareSessionProxy::~FakeSecureHardwareSessionProxy() { + if (id_ != 0) { + shutdown(); + } +} + +bool FakeSecureHardwareSessionProxy::initialize() { + if (id_ != 0) { + LOG(WARNING) << "Proxy is already initialized"; + return false; + } + bool initialized = eicSessionInit(&ctx_); + if (!initialized) { + return false; + } + optional<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; +} -FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {} +optional<uint64_t> FakeSecureHardwareSessionProxy::getAuthChallenge() { + if (!validateId(__func__)) { + return std::nullopt; + } -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()); + 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; } diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h index 6852c1a979..df98c7a121 100644 --- a/identity/aidl/default/FakeSecureHardwareProxy.h +++ b/identity/aidl/default/FakeSecureHardwareProxy.h @@ -27,21 +27,23 @@ namespace android::hardware::identity { // class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy { public: - FakeSecureHardwareProvisioningProxy(); + FakeSecureHardwareProvisioningProxy() = default; virtual ~FakeSecureHardwareProvisioningProxy(); bool initialize(bool testCredential) override; - bool initializeForUpdate(bool testCredential, string docType, - vector<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(); } diff --git a/identity/aidl/default/android.hardware.identity_credential.xml b/identity/aidl/default/android.hardware.identity_credential.xml index 5149792b7f..20b2710e1e 100644 --- a/identity/aidl/default/android.hardware.identity_credential.xml +++ b/identity/aidl/default/android.hardware.identity_credential.xml @@ -14,5 +14,5 @@ limitations under the License. --> <permissions> - <feature name="android.hardware.identity_credential" version="202101" /> + <feature name="android.hardware.identity_credential" version="202201" /> </permissions> diff --git a/identity/aidl/default/common/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp index 95557b5c9d..7678ecb918 100644 --- a/identity/aidl/default/common/IdentityCredential.cpp +++ b/identity/aidl/default/common/IdentityCredential.cpp @@ -72,14 +72,38 @@ int IdentityCredential::initialize() { testCredential_ = testCredentialItem->value(); encryptedCredentialKeys_ = encryptedCredentialKeysItem->value(); - if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys_)) { - LOG(ERROR) << "hwProxy->initialize failed"; - return false; + + // If in a session, delay the initialization of the proxy. + // + if (!session_) { + ndk::ScopedAStatus status = ensureHwProxy(); + if (!status.isOk()) { + LOG(ERROR) << "Error initializing hw proxy"; + return IIdentityCredentialStore::STATUS_FAILED; + } } return IIdentityCredentialStore::STATUS_OK; } +ndk::ScopedAStatus IdentityCredential::ensureHwProxy() { + if (hwProxy_) { + return ndk::ScopedAStatus::ok(); + } + hwProxy_ = hwProxyFactory_->createPresentationProxy(); + if (!hwProxy_) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error creating hw proxy")); + } + uint64_t sessionId = session_ ? session_->getSessionId() : EIC_PRESENTATION_ID_UNSET; + if (!hwProxy_->initialize(sessionId, testCredential_, docType_, encryptedCredentialKeys_)) { + hwProxy_.clear(); + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error initializing hw proxy")); + } + return ndk::ScopedAStatus::ok(); +} + ndk::ScopedAStatus IdentityCredential::deleteCredential( vector<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) { + ndk::ScopedAStatus status = ensureHwProxy(); + if (!status.isOk()) { + return status; + } + + // If in a session, ensure the passed-in session transcript matches the + // session transcript from the session. + if (session_) { + if (sessionTranscript != session_->getSessionTranscript()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_INVALID_DATA, - "SessionTranscript contains invalid CBOR")); + IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH, + "In a session and passed-in SessionTranscript doesn't match the one " + "from the session")); } - sessionTranscriptItem = std::move(item); } + if (numStartRetrievalCalls_ > 0) { if (sessionTranscript_ != sessionTranscript) { LOG(ERROR) << "Session Transcript changed"; @@ -390,32 +456,36 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } } - // TODO: move this check to the TA -#if 1 - // To prevent replay-attacks, we check that the public part of the ephemeral - // key we previously created, is present in the DeviceEngagement part of - // SessionTranscript as a COSE_Key, in uncompressed form. - // - // We do this by just searching for the X and Y coordinates. - if (sessionTranscript.size() > 0) { - auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_); - if (!getXYSuccess) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, - "Error extracting X and Y from ePub")); - } - if (sessionTranscript.size() > 0 && - !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(), - ePubX.size()) != nullptr && - memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(), - ePubY.size()) != nullptr)) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, - "Did not find ephemeral public key's X and Y coordinates in " - "SessionTranscript (make sure leading zeroes are not used)")); + if (session_) { + // If presenting in a session, the TA has already done this check. + + } else { + // To prevent replay-attacks, we check that the public part of the ephemeral + // key we previously created, is present in the DeviceEngagement part of + // SessionTranscript as a COSE_Key, in uncompressed form. + // + // We do this by just searching for the X and Y coordinates. + // + // Would be nice to move this check to the TA. + if (sessionTranscript.size() > 0) { + auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_); + if (!getXYSuccess) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + "Error extracting X and Y from ePub")); + } + if (sessionTranscript.size() > 0 && + !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(), + ePubX.size()) != nullptr && + memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(), + ePubY.size()) != nullptr)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + "Did not find ephemeral public key's X and Y coordinates in " + "SessionTranscript (make sure leading zeroes are not used)")); + } } } -#endif // itemsRequest: If non-empty, contains request data that may be signed by the // reader. The content can be defined in the way appropriate for the @@ -537,21 +607,38 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( // Finally, pass info so the HMAC key can be derived and the TA can start // creating the DeviceNameSpaces CBOR... - if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) { - // We expect the reader ephemeral public key to be same size and curve - // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH - // won't work. So its length should be 65 bytes and it should be - // starting with 0x04. - if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Reader public key is not in expected format")); + if (!session_) { + if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && + signingKeyBlob.size() > 0) { + // We expect the reader ephemeral public key to be same size and curve + // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH + // won't work. So its length should be 65 bytes and it should be + // starting with 0x04. + if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Reader public key is not in expected format")); + } + vector<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( diff --git a/identity/aidl/default/common/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h index ef9d13351c..2935fb80a7 100644 --- a/identity/aidl/default/common/IdentityCredential.h +++ b/identity/aidl/default/common/IdentityCredential.h @@ -30,6 +30,7 @@ #include <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_; diff --git a/identity/aidl/default/common/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp index e6b5466096..4703ffe646 100644 --- a/identity/aidl/default/common/IdentityCredentialStore.cpp +++ b/identity/aidl/default/common/IdentityCredentialStore.cpp @@ -20,6 +20,7 @@ #include "IdentityCredential.h" #include "IdentityCredentialStore.h" +#include "PresentationSession.h" #include "WritableIdentityCredential.h" namespace aidl::android::hardware::identity { @@ -61,9 +62,8 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential( "Unsupported cipher suite")); } - sp<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 diff --git a/identity/aidl/default/common/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h index d35e632984..77b894dbd6 100644 --- a/identity/aidl/default/common/IdentityCredentialStore.h +++ b/identity/aidl/default/common/IdentityCredentialStore.h @@ -47,6 +47,9 @@ class IdentityCredentialStore : public BnIdentityCredentialStore { ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData, shared_ptr<IIdentityCredential>* outCredential) override; + ndk::ScopedAStatus createPresentationSession( + CipherSuite cipherSuite, shared_ptr<IPresentationSession>* outSession) override; + private: sp<SecureHardwareProxyFactory> hwProxyFactory_; }; diff --git a/identity/aidl/default/common/PresentationSession.cpp b/identity/aidl/default/common/PresentationSession.cpp new file mode 100644 index 0000000000..fbd897281a --- /dev/null +++ b/identity/aidl/default/common/PresentationSession.cpp @@ -0,0 +1,149 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PresentationSession" + +#include "PresentationSession.h" +#include "IdentityCredentialStore.h" + +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <string.h> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include "FakeSecureHardwareProxy.h" +#include "IdentityCredential.h" +#include "PresentationSession.h" + +namespace aidl::android::hardware::identity { + +using ::std::optional; + +using namespace ::android::hardware::identity; + +PresentationSession::~PresentationSession() {} + +int PresentationSession::initialize() { + if (!hwProxy_->initialize()) { + LOG(ERROR) << "hwProxy->initialize failed"; + return IIdentityCredentialStore::STATUS_FAILED; + } + + optional<uint64_t> id = hwProxy_->getId(); + if (!id) { + LOG(ERROR) << "Error getting id for session"; + return IIdentityCredentialStore::STATUS_FAILED; + } + id_ = id.value(); + + optional<vector<uint8_t>> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair(); + if (!ephemeralKeyPriv) { + LOG(ERROR) << "Error getting ephemeral private key for session"; + return IIdentityCredentialStore::STATUS_FAILED; + } + optional<vector<uint8_t>> ephemeralKeyPair = + support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value()); + if (!ephemeralKeyPair) { + LOG(ERROR) << "Error creating ephemeral key-pair"; + return IIdentityCredentialStore::STATUS_FAILED; + } + ephemeralKeyPair_ = ephemeralKeyPair.value(); + + optional<uint64_t> authChallenge = hwProxy_->getAuthChallenge(); + if (!authChallenge) { + LOG(ERROR) << "Error getting authChallenge for session"; + return IIdentityCredentialStore::STATUS_FAILED; + } + authChallenge_ = authChallenge.value(); + + return IIdentityCredentialStore::STATUS_OK; +} + +ndk::ScopedAStatus PresentationSession::getEphemeralKeyPair(vector<uint8_t>* outKeyPair) { + *outKeyPair = ephemeralKeyPair_; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus PresentationSession::getAuthChallenge(int64_t* outChallenge) { + *outChallenge = authChallenge_; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus PresentationSession::setReaderEphemeralPublicKey( + const vector<uint8_t>& publicKey) { + // We expect the reader ephemeral public key to be same size and curve + // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH + // won't work. So its length should be 65 bytes and it should be + // starting with 0x04. + if (publicKey.size() != 65 || publicKey[0] != 0x04) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Reader public key is not in expected format")); + } + readerPublicKey_ = publicKey; + vector<uint8_t> pubKeyP256(publicKey.begin() + 1, publicKey.end()); + if (!hwProxy_->setReaderEphemeralPublicKey(pubKeyP256)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error setting readerEphemeralPublicKey for session")); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus PresentationSession::setSessionTranscript( + const vector<uint8_t>& sessionTranscript) { + sessionTranscript_ = sessionTranscript; + if (!hwProxy_->setSessionTranscript(sessionTranscript)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error setting SessionTranscript for session")); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus PresentationSession::getCredential( + const vector<uint8_t>& credentialData, shared_ptr<IIdentityCredential>* outCredential) { + shared_ptr<PresentationSession> p = ref<PresentationSession>(); + shared_ptr<IdentityCredential> credential = + ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, credentialData, p); + int ret = credential->initialize(); + if (ret != IIdentityCredentialStore::STATUS_OK) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + ret, "Error initializing IdentityCredential")); + } + *outCredential = std::move(credential); + + return ndk::ScopedAStatus::ok(); +} + +uint64_t PresentationSession::getSessionId() { + return id_; +} + +vector<uint8_t> PresentationSession::getSessionTranscript() { + return sessionTranscript_; +} + +vector<uint8_t> PresentationSession::getReaderEphemeralPublicKey() { + return readerPublicKey_; +} + +} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/common/PresentationSession.h b/identity/aidl/default/common/PresentationSession.h new file mode 100644 index 0000000000..76ca67b675 --- /dev/null +++ b/identity/aidl/default/common/PresentationSession.h @@ -0,0 +1,83 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H +#define ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H + +#include <aidl/android/hardware/identity/BnPresentationSession.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <vector> + +#include <cppbor.h> + +#include "IdentityCredentialStore.h" +#include "SecureHardwareProxy.h" + +namespace aidl::android::hardware::identity { + +using ::aidl::android::hardware::keymaster::HardwareAuthToken; +using ::aidl::android::hardware::keymaster::VerificationToken; +using ::android::sp; +using ::android::hardware::identity::SecureHardwareSessionProxy; +using ::std::vector; + +class PresentationSession : public BnPresentationSession { + public: + PresentationSession(sp<SecureHardwareProxyFactory> hwProxyFactory, + sp<SecureHardwareSessionProxy> hwProxy) + : hwProxyFactory_(std::move(hwProxyFactory)), hwProxy_(std::move(hwProxy)) {} + + virtual ~PresentationSession(); + + // Creates ephemeral key and auth-challenge in TA. Returns a status code from + // IIdentityCredentialStore. Must be called right after construction. + int initialize(); + + uint64_t getSessionId(); + + vector<uint8_t> getSessionTranscript(); + vector<uint8_t> getReaderEphemeralPublicKey(); + + // Methods from IPresentationSession follow. + ndk::ScopedAStatus getEphemeralKeyPair(vector<uint8_t>* outKeyPair) override; + ndk::ScopedAStatus getAuthChallenge(int64_t* outChallenge) override; + ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override; + ndk::ScopedAStatus setSessionTranscript(const vector<uint8_t>& sessionTranscript) override; + + ndk::ScopedAStatus getCredential(const vector<uint8_t>& credentialData, + shared_ptr<IIdentityCredential>* outCredential) override; + + private: + // Set by constructor + sp<SecureHardwareProxyFactory> hwProxyFactory_; + sp<SecureHardwareSessionProxy> hwProxy_; + + // Set by initialize() + uint64_t id_; + vector<uint8_t> ephemeralKeyPair_; + uint64_t authChallenge_; + + // Set by setReaderEphemeralPublicKey() + vector<uint8_t> readerPublicKey_; + + // Set by setSessionTranscript() + vector<uint8_t> sessionTranscript_; +}; + +} // namespace aidl::android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_PRESENTATIONSESSION_H diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h index a1ed1ef03b..a580444230 100644 --- a/identity/aidl/default/common/SecureHardwareProxy.h +++ b/identity/aidl/default/common/SecureHardwareProxy.h @@ -42,6 +42,7 @@ using ::std::vector; // Forward declare. // class SecureHardwareProvisioningProxy; +class SecureHardwareSessionProxy; class SecureHardwarePresentationProxy; // This is a class used to create proxies. @@ -52,6 +53,7 @@ class SecureHardwareProxyFactory : public RefBase { virtual ~SecureHardwareProxyFactory() {} virtual sp<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 diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml index a074250901..cc0ddc7d51 100644 --- a/identity/aidl/default/identity-default.xml +++ b/identity/aidl/default/identity-default.xml @@ -1,7 +1,7 @@ <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> diff --git a/identity/aidl/default/libeic/EicCommon.h b/identity/aidl/default/libeic/EicCommon.h index 476276ebcf..2a08a35f65 100644 --- a/identity/aidl/default/libeic/EicCommon.h +++ b/identity/aidl/default/libeic/EicCommon.h @@ -21,6 +21,9 @@ #ifndef ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H #define ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H +// KeyMint auth-challenges are 64-bit numbers and 0 typically means unset. +#define EIC_KM_AUTH_CHALLENGE_UNSET 0 + // Feature version 202009: // // CredentialKeys = [ diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h index d4fcf0e1bb..aa26e6202a 100644 --- a/identity/aidl/default/libeic/EicOps.h +++ b/identity/aidl/default/libeic/EicOps.h @@ -141,6 +141,10 @@ void* eicMemCpy(void* dest, const void* src, size_t n); // String length, see strlen(3). size_t eicStrLen(const char* s); +// Locate a substring, see memmem(3) +void* eicMemMem(const uint8_t* haystack, size_t haystackLen, const uint8_t* needle, + size_t needleLen); + // Memory compare, see CRYPTO_memcmp(3SSL) // // It takes an amount of time dependent on len, but independent of the contents of the @@ -151,6 +155,12 @@ int eicCryptoMemCmp(const void* s1, const void* s2, size_t n); // Random number generation. bool eicOpsRandom(uint8_t* buf, size_t numBytes); +// Creates a new non-zero identifier in |id|. +// +// Is guaranteed to be non-zero and different than what is already in |id|. +// +bool eicNextId(uint32_t* id); + // If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes). // // Otherwise returns all zeroes (16 bytes). @@ -295,6 +305,8 @@ bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t int verificationTokenSecurityLevel, const uint8_t* verificationTokenMac, size_t verificationTokenMacSize); +// Also see eicOpsLookupActiveSessionFromId() defined in EicSession.h + #ifdef __cplusplus } #endif diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c index 0d03ae9620..104a559697 100644 --- a/identity/aidl/default/libeic/EicPresentation.c +++ b/identity/aidl/default/libeic/EicPresentation.c @@ -16,11 +16,17 @@ #include "EicPresentation.h" #include "EicCommon.h" +#include "EicSession.h" #include <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)) { diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h index 6f7f432960..a031890e58 100644 --- a/identity/aidl/default/libeic/EicPresentation.h +++ b/identity/aidl/default/libeic/EicPresentation.h @@ -30,7 +30,13 @@ extern "C" { // The maximum size we support for public keys in reader certificates. #define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65 +// Constant used to convey that no session is associated with a presentation. +#define EIC_PRESENTATION_ID_UNSET 0 + typedef struct { + // A non-zero number unique for this EicPresentation instance + uint32_t id; + int featureLevel; uint8_t storageKey[EIC_AES_128_KEY_SIZE]; @@ -38,6 +44,10 @@ typedef struct { uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + // If non-zero (not EIC_PRESENTATION_ID_UNSET), the id of the EicSession object this + // presentation object is associated with. + uint32_t sessionId; + // The challenge generated with eicPresentationCreateAuthChallenge() uint64_t authChallenge; @@ -93,10 +103,18 @@ typedef struct { EicCbor cbor; } EicPresentation; -bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, - size_t docTypeLength, const uint8_t* encryptedCredentialKeys, +// If sessionId is zero (EIC_PRESENTATION_ID_UNSET), the presentation object is not associated +// with a session object. Otherwise it's the id of the session object. +// +bool eicPresentationInit(EicPresentation* ctx, uint32_t sessionId, bool testCredential, + const char* docType, size_t docTypeLength, + const uint8_t* encryptedCredentialKeys, size_t encryptedCredentialKeysSize); +bool eicPresentationShutdown(EicPresentation* ctx); + +bool eicPresentationGetId(EicPresentation* ctx, uint32_t* outId); + bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, size_t docTypeLength, time_t now, uint8_t* publicKeyCert, size_t* publicKeyCertSize, diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c index c9df4fd74f..a241b71b50 100644 --- a/identity/aidl/default/libeic/EicProvisioning.c +++ b/identity/aidl/default/libeic/EicProvisioning.c @@ -17,8 +17,21 @@ #include "EicProvisioning.h" #include "EicCommon.h" +#include <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, diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h index 92f1e4a2a0..d94f8f18c2 100644 --- a/identity/aidl/default/libeic/EicProvisioning.h +++ b/identity/aidl/default/libeic/EicProvisioning.h @@ -31,6 +31,9 @@ extern "C" { #define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32 typedef struct { + // A non-zero number unique for this EicProvisioning instance + uint32_t id; + // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate() uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; @@ -68,6 +71,10 @@ bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, con size_t docTypeLength, const uint8_t* encryptedCredentialKeys, size_t encryptedCredentialKeysSize); +bool eicProvisioningShutdown(EicProvisioning* ctx); + +bool eicProvisioningGetId(EicProvisioning* ctx, uint32_t* outId); + bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize, const uint8_t* applicationId, size_t applicationIdSize, uint8_t* publicKeyCert, diff --git a/identity/aidl/default/libeic/EicSession.c b/identity/aidl/default/libeic/EicSession.c new file mode 100644 index 0000000000..d0c7a0d77e --- /dev/null +++ b/identity/aidl/default/libeic/EicSession.c @@ -0,0 +1,120 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <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; +} diff --git a/identity/aidl/default/libeic/EicSession.h b/identity/aidl/default/libeic/EicSession.h new file mode 100644 index 0000000000..0303dae1c3 --- /dev/null +++ b/identity/aidl/default/libeic/EicSession.h @@ -0,0 +1,73 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_SESSION_H +#define ANDROID_HARDWARE_IDENTITY_EIC_SESSION_H + +#include "EicOps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // A non-zero number unique for this EicSession instance + uint32_t id; + + // The challenge generated at construction time by eicSessionInit(). + uint64_t authChallenge; + + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + + uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + + uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE]; + +} EicSession; + +bool eicSessionInit(EicSession* ctx); + +bool eicSessionShutdown(EicSession* ctx); + +bool eicSessionGetId(EicSession* ctx, uint32_t* outId); + +bool eicSessionGetAuthChallenge(EicSession* ctx, uint64_t* outAuthChallenge); + +bool eicSessionGetEphemeralKeyPair(EicSession* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]); + +bool eicSessionSetReaderEphemeralPublicKey( + EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]); + +bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize); + +// Looks up an active session with the given id. +// +// Returns NULL if no active session with the given id is found. +// +EicSession* eicSessionGetForId(uint32_t sessionId); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h index 20c889660f..d89fc9a0e3 100644 --- a/identity/aidl/default/libeic/libeic.h +++ b/identity/aidl/default/libeic/libeic.h @@ -27,10 +27,11 @@ extern "C" { */ #define EIC_INSIDE_LIBEIC_H #include "EicCbor.h" +#include "EicCommon.h" #include "EicOps.h" #include "EicPresentation.h" #include "EicProvisioning.h" -#include "EicCommon.h" +#include "EicSession.h" #undef EIC_INSIDE_LIBEIC_H #ifdef __cplusplus diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp index e5de91e22e..7b6f2c81ee 100644 --- a/identity/aidl/vts/Android.bp +++ b/identity/aidl/vts/Android.bp @@ -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", ], diff --git a/identity/aidl/vts/PresentationSessionTests.cpp b/identity/aidl/vts/PresentationSessionTests.cpp new file mode 100644 index 0000000000..88acb269e2 --- /dev/null +++ b/identity/aidl/vts/PresentationSessionTests.cpp @@ -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 |