Merge "Convert VtsHalDrmV1_*TargetTest to be parameterized test"
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index 2c5e5cc..2948ecb 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -234,7 +234,7 @@
{
.prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED),
.access = VehiclePropertyAccess::READ,
- .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
.minSampleRate = 1.0f,
.maxSampleRate = 10.0f,
},
diff --git a/current.txt b/current.txt
index 331a87d..853b8f8 100644
--- a/current.txt
+++ b/current.txt
@@ -615,13 +615,17 @@
ce8dbe76eb9ee94b46ef98f725be992e760a5751073d4f4912484026541371f3 android.hardware.health@2.1::IHealth
26f04510a0b57aba5167c5c0a7c2f077c2acbb98b81902a072517829fd9fd67f android.hardware.health@2.1::IHealthInfoCallback
db47f4ceceb1f06c656f39caa70c557b0f8471ef59fd58611bea667ffca20101 android.hardware.health@2.1::types
+0589e410f519e36514e7ece18f283f022df0f70efd2c12821d822f67f74aba98 android.hardware.identity@1.0::types
+bbeee9604128ede83ee755b67e73b5ad29e6e1dbac9ec41fea6ffe2745b0c50a android.hardware.identity@1.0::IIdentityCredential
+96ce8aad80f4c476f25261f790d357c117e79e18474c7dadd850dac704bbe65e android.hardware.identity@1.0::IIdentityCredentialStore
+6e1e28a96c90ba78d47257faea3f3bb4e6360affbbfa5822f0dc31211f9266ff android.hardware.identity@1.0::IWritableIdentityCredential
c228aaa27f66c48e147159a4f4996c5273191fece1b08de31bd171c61334855e android.hardware.keymaster@4.1::IKeymasterDevice
adb0efdf1462e9b2e742c0dcadd598666aac551f178be06e755bfcdf5797abd0 android.hardware.keymaster@4.1::IOperation
7a04ea5595ed418ca3e91c28b8bd7353dd988be9be7b0c8c9e64fb4b77bd4523 android.hardware.keymaster@4.1::types
9e59fffceed0dd72a9799e04505db5f777bbbea1af0695ba4107ef6d967c6fda android.hardware.neuralnetworks@1.3::IDevice
258825966435b3ed08832055bb736d81516013e405f161d9ccde9a90cfcdde83 android.hardware.neuralnetworks@1.3::IPreparedModel
94e803236398bed1febb11cc21051bc42ec003700139b099d6c479e02a7ca3c3 android.hardware.neuralnetworks@1.3::IPreparedModelCallback
-618a628f8c94d6f6e4cb401b69fa50ccb8b82191ea434e3a071252289b4f312c android.hardware.neuralnetworks@1.3::types
+f3c1e7298da628a755b452cd3325e8d0fe867a2debb873069baab6a27434a72d android.hardware.neuralnetworks@1.3::types
3e01d4446cd69fd1c48f8572efd97487bc179564b32bd795800b97bbe10be37b android.hardware.wifi@1.4::IWifi
a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardware.wifi.supplicant@1.3::ISupplicant
44445b8a03d7b9e68b2fbd954672c18a8fce9e32851b0692f4f4ab3407f86ecb android.hardware.wifi.supplicant@1.3::ISupplicantStaIface
diff --git a/drm/1.2/vts/functional/drm_hal_common.cpp b/drm/1.2/vts/functional/drm_hal_common.cpp
index 1f439f7..02aa3a9 100644
--- a/drm/1.2/vts/functional/drm_hal_common.cpp
+++ b/drm/1.2/vts/functional/drm_hal_common.cpp
@@ -132,7 +132,7 @@
contentConfigurations = vendorModule->getContentConfigurations();
// If drm scheme not installed skip subsequent tests
- if (!drmFactory->isCryptoSchemeSupported(getVendorUUID())) {
+ if (drmFactory.get() == nullptr || !drmFactory->isCryptoSchemeSupported(getVendorUUID())) {
vendorModule->setInstalled(false);
return;
}
diff --git a/drm/1.2/vts/functional/drm_hal_test.cpp b/drm/1.2/vts/functional/drm_hal_test.cpp
index 99c834d..54c5751 100644
--- a/drm/1.2/vts/functional/drm_hal_test.cpp
+++ b/drm/1.2/vts/functional/drm_hal_test.cpp
@@ -53,6 +53,7 @@
* Ensure drm factory supports module UUID Scheme
*/
TEST_P(DrmHalTest, VendorUuidSupported) {
+ RETURN_IF_SKIPPED;
auto res = drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kSwSecureCrypto);
ALOGI("kVideoMp4 = %s res %d", kVideoMp4, (bool)res);
EXPECT_TRUE(res);
@@ -62,6 +63,7 @@
* Ensure drm factory doesn't support an invalid scheme UUID
*/
TEST_P(DrmHalTest, InvalidPluginNotSupported) {
+ RETURN_IF_SKIPPED;
const uint8_t kInvalidUUID[16] = {
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
@@ -72,6 +74,7 @@
* Ensure drm factory doesn't support an empty UUID
*/
TEST_P(DrmHalTest, EmptyPluginUUIDNotSupported) {
+ RETURN_IF_SKIPPED;
hidl_array<uint8_t, 16> emptyUUID;
memset(emptyUUID.data(), 0, 16);
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(emptyUUID, kVideoMp4, kSwSecureCrypto));
@@ -81,6 +84,7 @@
* Ensure drm factory doesn't support an invalid mime type
*/
TEST_P(DrmHalTest, BadMimeNotSupported) {
+ RETURN_IF_SKIPPED;
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kBadMime, kSwSecureCrypto));
}
@@ -134,6 +138,7 @@
* A get key request should fail if no sessionId is provided
*/
TEST_P(DrmHalTest, GetKeyRequestNoSession) {
+ RETURN_IF_SKIPPED;
SessionId invalidSessionId;
hidl_vec<uint8_t> initData;
KeyedVector optionalParameters;
@@ -151,6 +156,7 @@
* invalid mime type
*/
TEST_P(DrmHalTest, GetKeyRequestBadMime) {
+ RETURN_IF_SKIPPED;
auto sessionId = openSession();
hidl_vec<uint8_t> initData;
KeyedVector optionalParameters;
@@ -187,6 +193,7 @@
* Test drm plugin offline key support
*/
TEST_P(DrmHalTest, OfflineLicenseTest) {
+ RETURN_IF_SKIPPED;
auto sessionId = openSession();
hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
@@ -226,6 +233,7 @@
* Test drm plugin offline key state
*/
TEST_P(DrmHalTest, OfflineLicenseStateTest) {
+ RETURN_IF_SKIPPED;
auto sessionId = openSession();
DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent(KeyType::OFFLINE);
hidl_vec<uint8_t> keySetId = loadKeys(sessionId, content, KeyType::OFFLINE);
@@ -250,6 +258,7 @@
* Negative offline license test. Remove empty keySetId
*/
TEST_P(DrmHalTest, RemoveEmptyKeySetId) {
+ RETURN_IF_SKIPPED;
KeySetId emptyKeySetId;
Status err = drmPlugin->removeOfflineLicense(emptyKeySetId);
EXPECT_EQ(Status::BAD_VALUE, err);
@@ -259,6 +268,7 @@
* Negative offline license test. Get empty keySetId state
*/
TEST_P(DrmHalTest, GetEmptyKeySetIdState) {
+ RETURN_IF_SKIPPED;
KeySetId emptyKeySetId;
auto res = drmPlugin->getOfflineLicenseState(emptyKeySetId, checkKeySetIdState<Status::BAD_VALUE, OfflineLicenseState::UNKNOWN>);
EXPECT_OK(res);
@@ -268,6 +278,7 @@
* Test that the plugin returns valid connected and max HDCP levels
*/
TEST_P(DrmHalTest, GetHdcpLevels) {
+ RETURN_IF_SKIPPED;
auto res = drmPlugin->getHdcpLevels_1_2(
[&](StatusV1_2 status, const HdcpLevel &connectedLevel,
const HdcpLevel &maxLevel) {
@@ -422,6 +433,7 @@
* Ensure clearkey drm factory doesn't support security level higher than supported
*/
TEST_P(DrmHalClearkeyTest, BadLevelNotSupported) {
+ RETURN_IF_SKIPPED;
const SecurityLevel kHwSecureAll = SecurityLevel::HW_SECURE_ALL;
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kHwSecureAll));
}
@@ -430,6 +442,7 @@
* Test resource contention during attempt to generate key request
*/
TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) {
+ RETURN_IF_SKIPPED;
Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorResourceContention);
EXPECT_EQ(Status::OK, status);
auto sessionId = openSession();
@@ -451,6 +464,7 @@
* Test clearkey plugin offline key with mock error
*/
TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) {
+ RETURN_IF_SKIPPED;
auto sessionId = openSession();
hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorInvalidState);
@@ -472,6 +486,7 @@
* Test SessionLostState is triggered on error
*/
TEST_P(DrmHalClearkeyTest, SessionLostState) {
+ RETURN_IF_SKIPPED;
sp<DrmHalPluginListener> listener = new DrmHalPluginListener();
auto res = drmPlugin->setListener(listener);
EXPECT_OK(res);
@@ -492,6 +507,7 @@
* Negative decrypt test. Decrypt with invalid key.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) {
+ RETURN_IF_SKIPPED;
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
@@ -529,6 +545,7 @@
* Negative decrypt test. Decrypt with a key exceeds AES_BLOCK_SIZE.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) {
+ RETURN_IF_SKIPPED;
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
diff --git a/identity/1.0/Android.bp b/identity/1.0/Android.bp
new file mode 100644
index 0000000..a5cea90
--- /dev/null
+++ b/identity/1.0/Android.bp
@@ -0,0 +1,25 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.identity@1.0",
+ root: "android.hardware",
+ vndk: {
+ enabled: true,
+ },
+ srcs: [
+ "types.hal",
+ "IIdentityCredential.hal",
+ "IIdentityCredentialStore.hal",
+ "IWritableIdentityCredential.hal",
+ ],
+ interfaces: [
+ "android.hidl.base@1.0",
+ "android.hardware.keymaster@4.0",
+ ],
+ types: [
+ "AuditLogEntry",
+ "ResultCode",
+ "SecureAccessControlProfile",
+ ],
+ gen_java: false,
+}
diff --git a/identity/1.0/IIdentityCredential.hal b/identity/1.0/IIdentityCredential.hal
new file mode 100644
index 0000000..75f6e18
--- /dev/null
+++ b/identity/1.0/IIdentityCredential.hal
@@ -0,0 +1,343 @@
+/*
+ * 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.
+ */
+
+package android.hardware.identity@1.0;
+
+import android.hardware.keymaster@4.0::HardwareAuthToken;
+
+interface IIdentityCredential {
+ /**
+ * Delete a credential.
+ *
+ * This method returns a COSE_Sign1 data structure signed by CredentialKey
+ * with payload set to the ProofOfDeletion CBOR below:
+ *
+ * ProofOfDeletion = [
+ * "ProofOfDeletion", ; tstr
+ * tstr, ; DocType
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ *
+ * After this method has been called, the persistent storage used for credentialData should
+ * be deleted.
+ *
+ * @return proofOfDeletionSignature is a COSE_Sign1 signature described above.
+ */
+ deleteCredential()
+ generates(Result result, vec<uint8_t> proofOfDeletionSignature);
+
+ /**
+ * Creates an ephemeral EC key pair, for use in establishing a seceure 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.
+ *
+ * This method may only be called once per instance. If called more than once, FAILED
+ * will be returned.
+ *
+ * @return result is OK on success or FAILED if an error occurred.
+ *
+ * @return keyPair contains the unencrypted key-pair in PKCS#8 format.
+ */
+ createEphemeralKeyPair() generates (Result result, vec<uint8_t> keyPair);
+
+ /**
+ * Sets the public part of the reader's ephemeral key pair.
+ *
+ * This method may only be called once per instance. If called more than once, FAILED
+ * will be returned.
+ *
+ * @param publicKey contains the reader's ephemeral public key, in uncompressed form.
+ *
+ * @return result is OK on success or FAILED if an error occurred.
+ */
+ setReaderEphemeralPublicKey(vec<uint8_t> publicKey) generates (Result result);
+
+ /**
+ * Creates a challenge value to be used for proving successful user authentication. This
+ * is included in the authToken passed to the startRetrieval() method.
+ *
+ * This method may only be called once per instance. If called more than once, FAILED
+ * will be returned.
+ *
+ * @return result is OK on success or FAILED if an error occurred.
+ *
+ * @return challenge on success, is a non-zero number.
+ */
+ createAuthChallenge() generates (Result result, uint64_t challenge);
+
+ /**
+ * Start an entry retrieval process.
+ *
+ * This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
+ * createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
+ * multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
+ * finishRetrieval().This whole process is called a "credential presentation".
+ *
+ * It is permissible to perform multiple credential presentations using the same instance (e.g.
+ * startRetrieval(), then multiple calls of startRetrieveEntryValue(), retrieveEntryValue(),
+ * then finally finishRetrieval()) but if this is done, the sessionTranscript parameter
+ * must be identical for each startRetrieval() invocation. If this is not the case, this call
+ * fails with the SESSION_TRANSCRIPT_MISMATCH error.
+ *
+ * If the provided authToken is not valid this method fails with INVALID_AUTH_TOKEN.
+ *
+ * Each of the provided accessControlProfiles is checked in this call. If they are not
+ * all valid, the call fails with INVALID_DATA.
+ *
+ * For the itemsRequest parameter, the content can be defined in the way appropriate for
+ * the credential, but there are three requirements that must be met to work with this HAL:
+ *
+ * 1. The content must be a CBOR-encoded structure.
+ * 2. The CBOR structure must be a map.
+ * 3. The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
+ * the example below.
+ *
+ * If these requirements are not met the startRetrieval() call fails with
+ * INVALID_ITEMS_REQUEST_MESSAGE.
+ *
+ * Here's an example of ItemsRequest CBOR which conforms to this requirement:
+ *
+ * ItemsRequest = {
+ * ? "docType" : DocType,
+ * "nameSpaces" : NameSpaces,
+ * ? "requestInfo" : {* tstr => any} ; Additional info the reader wants to provide
+ * }
+ *
+ * DocType = tstr
+ *
+ * NameSpaces = {
+ * + NameSpace => DataElements ; Requested data elements for each NameSpace
+ * }
+ *
+ * NameSpace = tstr
+ *
+ * DataElements = {
+ * + DataElement => IntentToRetain
+ * }
+ *
+ * DataElement = tstr
+ * IntentToRetain = bool
+ *
+ * For the readerSignature parameter, this can either be empty or if non-empty it
+ * must be a COSE_Sign1 structure with an ECDSA signature over the content of the
+ * CBOR conforming to the following CDDL:
+ *
+ * ReaderAuthentication = [
+ * "ReaderAuthentication",
+ * SessionTranscript,
+ * ItemsRequestBytes
+ * ]
+ *
+ * SessionTranscript = [
+ * DeviceEngagementBytes,
+ * EReaderKeyBytes
+ * ]
+ *
+ * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+ * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+ * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ *
+ * The public key corresponding to the key used to made signature, can be found in the
+ * 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described
+ * in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element
+ * and there may be more (and if so, each certificate must be signed by its successor).
+ * This is checked and if the check fails the call fails with READER_SIGNATURE_CHECK_FAILED.
+ *
+ * The SessionTranscript CBOR is conveyed in the sessionTranscript parameter. It
+ * is permissible for this to be empty in which case the readerSignature parameter
+ * must also be empty. If this is not the case, the call fails with FAILED.
+ *
+ * If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public
+ * part of the key-pair previously generated by createEphemeralKeyPair() must appear
+ * somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in
+ * uncompressed form. If this is not satisfied, the call fails with
+ * EPHEMERAL_PUBLIC_KEY_NOT_FOUND.
+ *
+ * @param accessControlProfiles
+ * Access control profiles that are required to retrieve the entries that are going to be
+ * requested with IIdentityCredential.retrieveEntryValue(). See above.
+ *
+ * @param authToken
+ * The authentication token that proves the user was authenticated, as required
+ * by one or more of the provided accessControlProfiles. See above.
+ *
+ * @param itemsRequest
+ * If non-empty, contains request data that is signed by the reader. See above.
+ *
+ * @param sessionTranscript
+ * Either empty or the CBOR of the SessionTranscript. See above.
+ *
+ * @param readerSignature
+ * readerSignature is either empty or contains a CBOR_Sign1 structure. See above.
+ *
+ * @param requestCounts
+ * requestCounts specifies the number of data items that are going to be requested, per
+ * namespace. The number of elements in the vector must be the number of namespaces for which
+ * data items will be requested in retrieveEntryValue() calls, and the values of the elments
+ * must be the number of items from each namespace, in order, that will be requested in
+ * retrieveEntryValue() calls.
+ * Note that it's the caller's responsibility to ensure that the counts correspond to the
+ * retrieveEntryValue() calls that will be made, and that every retrieveEntryValue() call
+ * will succeed (i.e. that the access control profile checks will succeed). This means that
+ * it's the responsibility of the caller to determine which access control checks will fail
+ * and remove the corresponding requests from the counts.
+ *
+ * @return result is OK on success. If an error occurs one of the values described above
+ * will be returned.
+ */
+ startRetrieval(vec<SecureAccessControlProfile> accessControlProfiles,
+ HardwareAuthToken authToken,
+ vec<uint8_t> itemsRequest,
+ vec<uint8_t> sessionTranscript,
+ vec<uint8_t> readerSignature,
+ vec<uint16_t> requestCounts) generates(Result result);
+
+ /**
+ * Starts retrieving an entry, subject to access control requirements. Entries must be
+ * retrieved in namespace groups as specified in the requestCounts parameter.
+ *
+ * If the requestData parameter as passed to startRetrieval() was non-empty
+ * this method must only be called with entries specified in that field. If this
+ * requirement is not met, the call fails with NOT_IN_REQUEST_MESSAGE.
+ *
+ * If nameSpace or name is empty this call fails with INVALID_DATA.
+ *
+ * Each access control profile for the entry is checked. If user authentication
+ * is required and the supplied auth token doesn't provide it the call fails
+ * with USER_AUTHENTICATION_FAILED. If reader authentication is required and
+ * a suitable reader certificate chain isn't presented, the call fails with
+ * READER_AUTHENTICATION_FAILED.
+ *
+ * It is permissible to keep retrieving values if an access control check fails.
+ *
+ * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+ *
+ * @param name is the name of the element.
+ *
+ * @param entrySize is the size of the entry value, if it's a text string or a byte string.
+ * It must be zero if the entry value is an integer or boolean. If this requirement
+ * is not met the call fails with INVALID_DATA.
+ *
+ * @param accessControlProfileIds specifies the set of access control profiles that can
+ * authorize access to the provisioned element. If an identifier of a profile
+ * is given and this profile wasn't passed to startRetrieval() this call fails
+ * with INVALID_DATA.
+ *
+ * @return result is OK on success. Otherwise one of INVALID_DATA, FAILED,
+ * USER_AUTHENTICATION_FAILED, READER_AUTHENTICATION_FAILED.
+ */
+ startRetrieveEntryValue(string nameSpace, string name, uint32_t entrySize,
+ vec<uint16_t> accessControlProfileIds)
+ generates (Result result);
+
+
+ /**
+ * Retrieves an entry value, or part of one, if the entry value is larger than gcmChunkSize.
+ * May only be called after startRetrieveEntry().
+ *
+ * If the passed in data is not authentic, can't be decrypted, is of the wrong size, or can't
+ * be decoded, this call fails with INVALID_DATA.
+ *
+ * @param encryptedContent contains the encrypted and MACed content.
+ *
+ * @return result is OK on success, INVALID_DATA, or FAILED if an error occurred.
+ *
+ * @return content is the entry value as CBOR, or part of the entry value in the case the
+ * content exceeds gcmChunkSize in length.
+ */
+ retrieveEntryValue(vec<uint8_t> encryptedContent)
+ generates (Result result, vec<uint8_t> content);
+
+
+ /**
+ * End retrieval of data, optionally returning a message authentication code over the
+ * returned data.
+ *
+ * If signingKeyBlob or the sessionTranscript parameter passed to startRetrieval() is
+ * empty then the returned MAC will be empty.
+ *
+ * @param signingKeyBlob is either empty or a signingKeyBlob (see generateSigningKeyPair(),
+ * below) containing the signing key to use to sign the data retrieved. If this
+ * is not in the right format the call fails with INVALID_DATA.
+ *
+ * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+ *
+ * @return mac is empty if signingKeyBlob or the sessionTranscript passed to
+ * startRetrieval() is empty. Otherwise it is a COSE_Mac0 with empty payload
+ * and the detached content is set to DeviceAuthentication as defined below.
+ * The key used for the MAC operation is EMacKey and is derived as follows:
+ *
+ * KDF(ECDH(SDeviceKey.Priv, EReaderKey.Pub))
+ *
+ * where SDeviceKey.Priv is the key identified by signingKeyBlob. The KDF
+ * and ECDH functions shall be the same as the ciphersuite selected and
+ * passed to IIdentityStore.getCredential(). The EMacKey shall be derived
+ * using a salt of 0x00.
+ *
+ * DeviceAuthentication = [
+ * "DeviceAuthentication",
+ * SessionTranscript,
+ * DocType,
+ * DeviceNameSpaceBytes,
+ * ]
+ *
+ * DocType = tstr
+ *
+ * SessionTranscript = [
+ * DeviceEngagementBytes,
+ * EReaderKeyBytes
+ * ]
+ *
+ * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+ * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+ * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ *
+ * where
+ *
+ * DeviceNameSpaces = {
+ * * NameSpace => DeviceSignedItems
+ * }
+ * DeviceSignedItems = {
+ * + DataItemName => DataItemValue
+ * }
+ *
+ * Namespace = tstr
+ * DataItemName = tstr
+ * DataItemValue = any
+ *
+ *
+ * @return deviceNameSpaces the bytes of DeviceNameSpaces.
+ */
+ finishRetrieval(vec<uint8_t> signingKeyBlob)
+ generates(Result result, vec<uint8_t> mac, vec<uint8_t> deviceNameSpaces);
+
+
+ /**
+ * Generate a key pair to be used for signing session data and retrieved data items.
+ *
+ * @return result is OK on success or FAILED if an error occurred.
+ *
+ * @return signingKeyBlob contains an encrypted copy of the newly-generated private signing key.
+ *
+ * @return signingKeyCertificate contains an X.509 certificate for the new signing key, signed
+ * by the credential key.
+ */
+ generateSigningKeyPair()
+ generates(Result result, vec<uint8_t> signingKeyBlob,
+ vec<uint8_t> signingKeyCertificate);
+};
diff --git a/identity/1.0/IIdentityCredentialStore.hal b/identity/1.0/IIdentityCredentialStore.hal
new file mode 100644
index 0000000..118ca6f
--- /dev/null
+++ b/identity/1.0/IIdentityCredentialStore.hal
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+package android.hardware.identity@1.0;
+
+import IWritableIdentityCredential;
+import IIdentityCredential;
+
+/**
+ * IIdentityCredentialStore provides an interface to a secure store for user identity documents.
+ * This HAL is deliberately fairly general and abstract. To the extent possible, specification of
+ * the message formats and semantics of communication with credential verification devices and
+ * issuing authorities (IAs) is out of scope for this HAL. It provides the interface with secure
+ * storage but a credential-specific Android application will be required to implement the
+ * presentation and verification protocols and processes appropriate for the specific credential
+ * type.
+ *
+ * The design of this HAL makes few assumptions about the underlying secure hardware. In particular
+ * it does not assume that the secure hardware has any storage beyond that needed for a persistent,
+ * hardware-bound AES-128 key. However, its design allows the use of secure hardware that does have
+ * storage, specifically to enable "direct access". Most often credentials will be accessed through
+ * this HAL and through the Android layers above it but that requires that the Android device be
+ * powered up and fully functional. It is desirable to allow identity credential usage when the
+ * Android device's battery is too low to boot the Android operating system, so direct access to the
+ * secure hardware via NFC may allow data retrieval, if the secure hardware chooses to implement it.
+ * Definition of how data is retrieved in low power mode is explicitly out of scope for this HAL
+ * specification; it's up to the relevant identity credential standards to define.
+ *
+ * The 'default' HAL instance is explicitly not for direct access and the 'direct_access' HAL
+ * instance - if available - is for direct access. Applications are expected to provision their
+ * credential to both instances (and the contents may differ), not just one of them.
+ *
+ * Multiple credentials can be created. Each credential comprises:
+ *
+ * - A document type, which is a UTF-8 string of at most 256 bytes.
+ *
+ * - A set of namespaces, which serve to disambiguate value names. Namespaces are UTF-8 strings of
+ * up to 256 bytes in length (most should be much shorter). It is recommended that namespaces be
+ * structured as reverse domain names so that IANA effectively serves as the namespace registrar.
+ *
+ * - For each namespase, a set of name/value pairs, each with an associated set of access control
+ * profile IDs. Names are UTF-8 strings of up to 256 bytes in length (most should be much
+ * shorter). Values stored must be encoed as valid CBOR (https://tools.ietf.org/html/rfc7049) and
+ * the encoeded size is is limited to at most 512 KiB.
+ *
+ * - A set of access control profiles, each with a profile ID and a specification of the
+ * conditions which satisfy the profile's requirements.
+ *
+ * - An asymmetric key pair which is used to authenticate the credential to the IA, called the
+ * CredentialKey. This key is attested to by the secure hardware using Android Keystore
+ * attestation (https://source.android.com/security/keystore/attestation). See
+ * getAttestationCertificate() in the IWritableIdentityCredential for more information.
+ *
+ * - A set of zero or more named reader authentication public keys, which are used to authenticate
+ * an authorized reader to the credential.
+ *
+ * - A set of named signing keys, which are used to sign collections of values and session
+ * transcripts.
+ *
+ * Cryptographic notation:
+ *
+ * Throughout this HAL, cryptographic operations are specified in detail. To avoid repeating the
+ * definition of the notation, it's specified here. It is assumed that the reader is familiar with
+ * standard cryptographic algorithms and constructs.
+ *
+ * AES-GCM-ENC(K, N, D, A) represents AES-GCM encryption with key 'K', nonce 'N', additional
+ * authenticated data 'A' and data 'D'. The nonce is usually specified as 'R', meaning 12
+ * random bytes. The nonce is always 12 bytes and is prepended to the ciphertext. The GCM
+ * tag is 16 bytes, appended to the ciphertext. AES-GCM-DEC with the same argument notation
+ * represents the corresponding decryption operation.
+ *
+ * ECDSA(K, D) represents ECDSA signing of data 'D' with key 'K'.
+ *
+ * || represents concatenation
+ *
+ * {} represents an empty input; 0 bytes of data.
+ *
+ * HBK represents a device-unique, hardware-bound AES-128 key which exists only in secure
+ * hardware, except for "test" credential stores (see createCredential(), below). For test
+ * stores, an all-zero value is used in place of the HBK.
+ *
+ * Data encoding notation:
+ *
+ * Various fields need to be encoded as precisely-specified byte arrays. Where existing standards
+ * define appropriate encodings, those are used. For example, X.509 certificates. Where new
+ * encodings are needed, CBOR is used. CBOR maps are described in CDDL notation
+ * (https://tools.ietf.org/html/draft-ietf-cbor-cddl-06).
+ */
+interface IIdentityCredentialStore {
+
+ /**
+ * Returns information about hardware.
+ *
+ * The isDirectAccess output parameter indicates whether this credential store
+ * implementation is for direct access. Credentials provisioned in credential
+ * stores with this set to true, should use reader authentication on all data elements.
+ *
+ * @return result is OK on success, FAILED if an error occurred.
+ *
+ * @return credentialStoreName the name of the credential store implementation.
+ *
+ * @return credentialStoreAuthorName the name of the credential store author.
+ *
+ * @return dataChunkSize the maximum size of data chunks.
+ *
+ * @return isDirectAccess whether the provisioned credential is available through
+ * direct access.
+ *
+ * @return supportedDocTypes if empty, then any document type is supported, otherwise
+ * only the document types returned are supported.
+ */
+ getHardwareInformation()
+ generates(Result result,
+ string credentialStoreName,
+ string credentialStoreAuthorName,
+ uint32_t dataChunkSize,
+ bool isDirectAccess,
+ vec<string> supportedDocTypes);
+
+ /**
+ * createCredential creates a new Credential. When a Credential is created, two cryptographic
+ * keys are created: StorageKey, an AES key used to secure the externalized Credential
+ * contents, and CredentialKeyPair, an EC key pair used to authenticate the store to the IA. In
+ * addition, all of the Credential data content is imported and a certificate for the
+ * CredentialKeyPair and a signature produced with the CredentialKeyPair are created. These
+ * latter values may be checked by an issuing authority to verify that the data was imported
+ * into secure hardware and that it was imported unmodified.
+ *
+ * @param docType is an optional name (may be an empty string) that identifies the type of
+ * credential being created, e.g. "org.iso.18013-5.2019.mdl" (the doc type of the ISO
+ * driving license standard).
+ *
+ * @param testCredential indicates if this is a test store. Test credentials must use an
+ * all-zeros hardware-bound key (HBK) and must set the test bit in the
+ * personalizationReceipt (see finishAddingEntries(), in IWritableIdentityCredential).
+ *
+ * @return result is OK on success, FAILED if an error occurred.
+ *
+ * @return writableCredential is an IWritableIdentityCredential HIDL interface that provides
+ * operations to provision a credential.
+ */
+ createCredential(string docType, bool testCredential)
+ generates(Result result, IWritableIdentityCredential writableCredential);
+
+ /**
+ * getCredential retrieves an IIdentityCredential HIDL interface which allows use of a stored
+ * Credential.
+ *
+ * The cipher suite used to communicate with the remote verifier must also be specified. Currently
+ * only a single cipher-suite is supported and the details of this are as follow:
+ *
+ * - ECDHE with HKDF-SHA-256 for key agreement.
+ * - AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
+ * for every message).
+ * - ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
+ * man-in-the-middle attacks), signing keys are not ephemeral.
+ *
+ * Support for other cipher suites may be added in a future version of this HAL.
+ *
+ * @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 finishAddingEntries(), in IWritableIdentityCredential.
+ *
+ * @return result is OK on success or INVALID_DATA if the passed in credentialData
+ * cannot be decoded or decrypted.
+ *
+ * @return credential is an IIdentityCredential HIDL interface that provides operations on the
+ * Credential.
+ */
+ getCredential(vec<uint8_t> credentialData)
+ generates (Result result, IIdentityCredential credential);
+};
diff --git a/identity/1.0/IWritableIdentityCredential.hal b/identity/1.0/IWritableIdentityCredential.hal
new file mode 100644
index 0000000..b1ce00d
--- /dev/null
+++ b/identity/1.0/IWritableIdentityCredential.hal
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+package android.hardware.identity@1.0;
+
+/**
+ * IWritableIdentityCredential is used to personalize a new identity credential. Credentials cannot
+ * be updated or modified after creation; any changes require deletion and re-creation.
+ */
+interface IWritableIdentityCredential {
+ /**
+ * Gets the certificate chain for credentialKey which can be used to prove the hardware
+ * characteristics to an issuing authority. Must not be called more than once.
+ *
+ * The certificate chain must be generated using Keymaster Attestation
+ * (see https://source.android.com/security/keystore/attestation) and must also
+ * have the Tag::IDENTITY_CREDENTIAL_KEY tag from KeyMaster 4.1 set. This tag indicates
+ * that this key is an Identity Credential key (which can only sign/MAC very
+ * specific messages) and not an Android Keystore key (which can be used to sign/MAC
+ * anything).
+ *
+ * @param attestationChallenge a challenge set by the issuer to ensure freshness.
+ *
+ * @return result is OK on success, FAILED if an error occurred.
+ *
+ * @return certificate is the X.509 certificate chain for the credentialKey
+ */
+ getAttestationCertificate(vec<uint8_t> attestationChallenge)
+ generates(Result result, vec<uint8_t> certificate);
+
+ /**
+ * Start the personalization process.
+ *
+ * startPersonalization must not be called more than once.
+ *
+ * @param accessControlProfileCount specifies the number of access control profiles that will be
+ * provisioned with addAccessControlProfile().
+ *
+ * @param entryCounts specifies the number of data entries that will be provisioned with
+ * beginAddEntry() and addEntry(). Each item in the array specifies how many entries
+ * will be added for each name space.
+ *
+ * @return result is OK on success, FAILED if an error occurred.
+ *
+ */
+ startPersonalization(uint16_t accessControlProfileCount, vec<uint16_t> entryCounts)
+ generates(Result result);
+
+ /**
+ * Add an access control profile, which defines the requirements or retrieval of one or more
+ * entries. If both readerCertificate and userAuthenticationRequired are empty/false,
+ * associated entries are open access, requiring no authentication to read (though the caller
+ * is free to require other authentication above this HAL).
+ *
+ * This method must be called exactly as many times as specified in the startPersonalization()
+ * accessControlProfileCount parameter. If this is requirement is not met, the method fails
+ * with INVALID_DATA.
+ *
+ * @param id a numeric identifier that must be unique within the context of a Credential and may
+ * be used to reference the profile. If this is not satisfied the call fails with
+ * INVALID_DATA.
+ *
+ * @param readerCertificate if non-empty, specifies a X.509 certificate (or chain of certificates)
+ * that must be used to authenticate requests (see the readerSignature parameter in
+ * IIdentityCredential.startRetrieval).
+ *
+ * @param userAuthenticationRequired if true, specifies that the user is required to
+ * authenticate to allow requests. Required authentication freshness is specified by
+ * timeout below.
+ *
+ * @param timeoutMillis specifies the amount of time, in milliseconds, for which a user
+ * authentication (see userAuthenticationRequired above) is valid, if
+ * userAuthenticationRequired is true. If the timout is zero then authentication is
+ * required for each reader session. If userAuthenticationRequired is false, the timeout
+ * must be zero. If this requirement is not met the call fails with INVALID_DATA.
+ *
+ * @param secureUserId must be non-zero if userAuthenticationRequired is true. It is not
+ * related to any Android user ID or UID, but is created in the Gatekeeper application
+ * in the secure environment. If this requirement is not met the call fails with
+ * INVALID_DATA.
+ *
+ * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+ *
+ * @return secureAccessControlProfile is a structure with the passed-in data and MAC created
+ * with storageKey for authenticating the data at a later point in time.
+ */
+ addAccessControlProfile(uint16_t id, vec<uint8_t> readerCertificate,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId)
+ generates(Result result, SecureAccessControlProfile secureAccessControlProfile);
+
+ /**
+ * Begins the process of adding an entry to the credential. All access control profiles must be
+ * added before calling this method. Entries must be added in namespace "groups", meaning all
+ * entries of one namespace must be added before adding entries from another namespace.
+ *
+ * This method must be called exactly as many times as the sum of the items in the entryCounts
+ * parameter specified in the startPersonalization(), and must be followed by one or more calls
+ * to addEntryValue(). If this requirement is not met the method fails with INVALID_DATA.
+ *
+ * @param accessControlProfileIds specifies the set of access control profiles that can
+ * authorize access to the provisioned element.
+ *
+ * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+ *
+ * @param name is the name of the element.
+ *
+ * @param entrySize is the size of the entry value. If this requirement
+ * is not met this method fails with INVALID_DATA.
+ *
+ * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+ */
+ beginAddEntry(vec<uint16_t> accessControlProfileIds, string nameSpace,
+ string name, uint32_t entrySize)
+ generates(Result result);
+
+ /**
+ * Continues the process of adding an entry, providing a value or part of a value.
+ *
+ * In the common case, this method will be called only once per entry added. In the case of
+ * values that are larger than the secure environment's GCM chunk size
+ * (see IIdentityCredentialStore.getHardwareInformation()), the caller must provide the
+ * value in chunks. All chunks must be exactly gcmChunkSize except the last and the sum of all
+ * chunk sizes must equal the value of the beginAddEntry() entrySize argument. If this
+ * requirement is not met the call fails with INVALID_DATA.
+ *
+ * @param content is the entry value, encoded as CBOR. In the case the content exceeds gcmChunkSize,
+ * this may be partial content up to gcmChunkSize bytes long.
+ *
+ * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+ *
+ * @return encryptedContent contains the encrypted and MACed content. For directly-available
+ * credentials the contents are implementation-defined but must not exceed 32 bytes in
+ * length.
+ *
+ * For other credentials, encryptedContent contains:
+ *
+ * AES-GCM-ENC(storageKey, R, Data, AdditionalData)
+ *
+ * where:
+ *
+ * Data = any ; value
+ *
+ * AdditionalData = {
+ * "Namespace" : tstr,
+ * "Name" : tstr,
+ * "AccessControlProfileIds" : [ + uint ],
+ * }
+ */
+ addEntryValue(vec<uint8_t> content)
+ generates(Result result, vec<uint8_t> encryptedContent);
+
+ /**
+ * Finishes adding entries and returns a signature that an issuing authority may use to validate
+ * that all data was provisioned correctly.
+ *
+ * After this method is called, the IWritableIdentityCredential is no longer usable.
+ *
+ * @return result is OK on success or FAILED if an error occurred.
+ *
+ * @return credentialData is a CBOR-encoded structure (in CDDL notation):
+ *
+ * CredentialData = [
+ * tstr, ; docType, an optional name that identifies the type of credential
+ * bool, ; testCredential, indicates if this is a test credential
+ * bstr ; an opaque byte vector with encrypted data, see below
+ * ]
+ *
+ * The last element is an opaque byte vector which contains encrypted copies of the
+ * secrets used to secure the new credential's data and to authenticate the credential to
+ * the issuing authority. It contains:
+ *
+ * AES-GCM-ENC(HBK, R, CredentialKeys, docType)
+ *
+ * where HBK is a unique hardware-bound key that has never existed outside of the secure
+ * environment (except it's all zeroes if testCredential is True) and CredentialKeys is
+ * the CBOR-encoded structure (in CDDL notation):
+ *
+ * CredentialKeys = [
+ * bstr, ; storageKey, a 128-bit AES key
+ * bstr ; credentialPrivKey, the private key for credentialKey
+ * ]
+ *
+ * @return proofOfProvisioningSignature proves to the IA that the credential was imported into the
+ * secure hardware without alteration or error. When the final addEntry() call is made
+ * (when the number of provisioned entries equals the sum of the items in
+ * startPersonalization() entryCounts parameter), it a COSE_Sign1 structure
+ * signed by CredentialKey with payload set to the ProofOfProvisioning CBOR below:
+ *
+ * ProofOfProvisioning = [
+ * "ProofOfProvisioning",
+ * tstr, ; DocType
+ * [ * AccessControlProfile ],
+ * ProvisionedData,
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ *
+ * AccessControlProfile = {
+ * "id" : uint,
+ * ? "readerCertificate" : bstr,
+ * ? (
+ * "userAuthenticationRequired" : bool,
+ * "timeoutMillis" : uint,
+ * )
+ * }
+ *
+ * ProvisionedData = {
+ * * Namespace => [ + Entry ]
+ * },
+ *
+ * Namespace = tstr
+ *
+ * Entry = {
+ * "name" : tstr,
+ * "value" : any,
+ * "accessControlProfiles" : [ * uint ],
+ * }
+ */
+ finishAddingEntries()
+ generates(Result result, vec<uint8_t> credentialData,
+ vec<uint8_t> proofOfProvisioningSignature);
+};
diff --git a/identity/1.0/default/Android.bp b/identity/1.0/default/Android.bp
new file mode 100644
index 0000000..d2b2966
--- /dev/null
+++ b/identity/1.0/default/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2019 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.
+//
+
+cc_binary {
+ name: "android.hardware.identity@1.0-service.example",
+ init_rc: ["android.hardware.identity@1.0-service.example.rc"],
+ vendor: true,
+ relative_install_path: "hw",
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ srcs: [
+ "service.cpp",
+ "IdentityCredential.cpp",
+ "IdentityCredentialStore.cpp",
+ "WritableIdentityCredential.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.identity@1.0",
+ "android.hardware.identity-support-lib",
+ "android.hardware.keymaster@4.0",
+ "libcppbor",
+ "libcrypto",
+ "libbase",
+ "libhidlbase",
+ "liblog",
+ "libutils",
+ ],
+}
diff --git a/identity/1.0/default/IdentityCredential.cpp b/identity/1.0/default/IdentityCredential.cpp
new file mode 100644
index 0000000..b0a5e56
--- /dev/null
+++ b/identity/1.0/default/IdentityCredential.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright 2019, 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 "IdentityCredential"
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::android::hardware::keymaster::V4_0::Timestamp;
+using ::std::optional;
+
+Return<void> IdentityCredential::deleteCredential(deleteCredential_cb _hidl_cb) {
+ cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+ vector<uint8_t> proofOfDeletion = array.encode();
+
+ optional<vector<uint8_t>> proofOfDeletionSignature =
+ support::coseSignEcDsa(credentialPrivKey_,
+ proofOfDeletion, // payload
+ {}, // additionalData
+ {}); // certificateChain
+ if (!proofOfDeletionSignature) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error signing data"), {});
+ return Void();
+ }
+
+ _hidl_cb(support::resultOK(), proofOfDeletionSignature.value());
+ return Void();
+}
+
+Return<void> IdentityCredential::createEphemeralKeyPair(createEphemeralKeyPair_cb _hidl_cb) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ if (!keyPair) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error creating ephemeral key pair"), {});
+ return Void();
+ }
+
+ // Stash public key of this key-pair for later check in startRetrieval().
+ optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ if (!publicKey) {
+ _hidl_cb(support::result(ResultCode::FAILED,
+ "Error getting public part of ephemeral key pair"),
+ {});
+ return Void();
+ }
+ ephemeralPublicKey_ = publicKey.value();
+
+ _hidl_cb(support::resultOK(), keyPair.value());
+ return Void();
+}
+
+Return<void> IdentityCredential::setReaderEphemeralPublicKey(
+ const hidl_vec<uint8_t>& publicKey, setReaderEphemeralPublicKey_cb _hidl_cb) {
+ readerPublicKey_ = publicKey;
+ _hidl_cb(support::resultOK());
+ return Void();
+}
+
+ResultCode IdentityCredential::initialize() {
+ auto [item, _, message] = cppbor::parse(credentialData_);
+ if (item == nullptr) {
+ LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+ return ResultCode::INVALID_DATA;
+ }
+
+ const cppbor::Array* arrayItem = item->asArray();
+ if (arrayItem == nullptr || arrayItem->size() != 3) {
+ LOG(ERROR) << "CredentialData is not an array with three elements";
+ return ResultCode::INVALID_DATA;
+ }
+
+ const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+ const cppbor::Bool* testCredentialItem =
+ ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
+ : nullptr);
+ const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+ if (docTypeItem == nullptr || testCredentialItem == nullptr ||
+ encryptedCredentialKeysItem == nullptr) {
+ LOG(ERROR) << "CredentialData unexpected item types";
+ return ResultCode::INVALID_DATA;
+ }
+
+ docType_ = docTypeItem->value();
+ testCredential_ = testCredentialItem->value();
+
+ vector<uint8_t> hardwareBoundKey;
+ if (testCredential_) {
+ hardwareBoundKey = support::getTestHardwareBoundKey();
+ } else {
+ hardwareBoundKey = support::getHardwareBoundKey();
+ }
+
+ const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
+ const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
+ optional<vector<uint8_t>> decryptedCredentialKeys =
+ support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
+ if (!decryptedCredentialKeys) {
+ LOG(ERROR) << "Error decrypting CredentialKeys";
+ return ResultCode::INVALID_DATA;
+ }
+
+ auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+ if (dckItem == nullptr) {
+ LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
+ return ResultCode::INVALID_DATA;
+ }
+ const cppbor::Array* dckArrayItem = dckItem->asArray();
+ if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
+ LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
+ return ResultCode::INVALID_DATA;
+ }
+ const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
+ const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
+ if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
+ LOG(ERROR) << "CredentialKeys unexpected item types";
+ return ResultCode::INVALID_DATA;
+ }
+ storageKey_ = storageKeyItem->value();
+ credentialPrivKey_ = credentialPrivKeyItem->value();
+
+ return ResultCode::OK;
+}
+
+Return<void> IdentityCredential::createAuthChallenge(createAuthChallenge_cb _hidl_cb) {
+ uint64_t challenge = 0;
+ while (challenge == 0) {
+ optional<vector<uint8_t>> bytes = support::getRandom(8);
+ if (!bytes) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting random data for challenge"),
+ 0);
+ return Void();
+ }
+
+ challenge = 0;
+ for (size_t n = 0; n < bytes.value().size(); n++) {
+ challenge |= ((bytes.value())[n] << (n * 8));
+ }
+ }
+
+ authChallenge_ = challenge;
+ _hidl_cb(support::resultOK(), challenge);
+ return Void();
+}
+
+// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
+// ahead of time.
+bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
+ const vector<uint8_t>& readerCertificateChain) {
+ optional<vector<uint8_t>> acpPubKey =
+ support::certificateChainGetTopMostKey(profile.readerCertificate);
+ if (!acpPubKey) {
+ LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
+ return false;
+ }
+
+ optional<vector<vector<uint8_t>>> certificatesInChain =
+ support::certificateChainSplit(readerCertificateChain);
+ if (!certificatesInChain) {
+ LOG(ERROR) << "Error splitting readerCertificateChain";
+ return false;
+ }
+ for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
+ optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
+ if (!certPubKey) {
+ LOG(ERROR)
+ << "Error extracting public key from certificate in chain presented by reader";
+ return false;
+ }
+ if (acpPubKey.value() == certPubKey.value()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Timestamp clockGetTime() {
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ return time.tv_sec * 1000 + time.tv_nsec / 1000000;
+}
+
+bool checkUserAuthentication(const SecureAccessControlProfile& profile,
+ const HardwareAuthToken& authToken, uint64_t authChallenge) {
+ if (profile.secureUserId != authToken.userId) {
+ LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
+ << ") differs from userId in authToken (" << authToken.userId << ")";
+ return false;
+ }
+
+ if (profile.timeoutMillis == 0) {
+ if (authToken.challenge == 0) {
+ LOG(ERROR) << "No challenge in authToken";
+ return false;
+ }
+
+ if (authToken.challenge != authChallenge) {
+ LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
+ return false;
+ }
+ return true;
+ }
+
+ // Note that the Epoch for timestamps in HardwareAuthToken is at the
+ // discretion of the vendor:
+ //
+ // "[...] since some starting point (generally the most recent device
+ // boot) which all of the applications within one secure environment
+ // must agree upon."
+ //
+ // Therefore, if this software implementation is used on a device which isn't
+ // the emulator then the assumption that the epoch is the same as used in
+ // clockGetTime above will not hold. This is OK as this software
+ // implementation should never be used on a real device.
+ //
+ Timestamp now = clockGetTime();
+ if (authToken.timestamp > now) {
+ LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp
+ << ") is in the future (now: " << now << ")";
+ return false;
+ }
+ if (now > authToken.timestamp + profile.timeoutMillis) {
+ LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp << " + "
+ << profile.timeoutMillis << " = "
+ << (authToken.timestamp + profile.timeoutMillis)
+ << ") is in the past (now: " << now << ")";
+ return false;
+ }
+
+ return true;
+}
+
+Return<void> IdentityCredential::startRetrieval(
+ const hidl_vec<SecureAccessControlProfile>& accessControlProfiles,
+ const HardwareAuthToken& authToken, const hidl_vec<uint8_t>& itemsRequest,
+ const hidl_vec<uint8_t>& sessionTranscript, const hidl_vec<uint8_t>& readerSignature,
+ const hidl_vec<uint16_t>& requestCounts, startRetrieval_cb _hidl_cb) {
+ if (sessionTranscript.size() > 0) {
+ auto [item, _, message] = cppbor::parse(sessionTranscript);
+ if (item == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "SessionTranscript contains invalid CBOR"));
+ return Void();
+ }
+ sessionTranscriptItem_ = std::move(item);
+ }
+ if (numStartRetrievalCalls_ > 0) {
+ if (sessionTranscript_ != vector<uint8_t>(sessionTranscript)) {
+ _hidl_cb(support::result(
+ ResultCode::SESSION_TRANSCRIPT_MISMATCH,
+ "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
+ return Void();
+ }
+ }
+ sessionTranscript_ = sessionTranscript;
+
+ // If there is a signature, validate that it was made with the top-most key in the
+ // certificate chain embedded in the COSE_Sign1 structure.
+ optional<vector<uint8_t>> readerCertificateChain;
+ if (readerSignature.size() > 0) {
+ readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
+ if (!readerCertificateChain) {
+ _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+ "Unable to get reader certificate chain from COSE_Sign1"));
+ return Void();
+ }
+
+ if (!support::certificateChainValidate(readerCertificateChain.value())) {
+ _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+ "Error validating reader certificate chain"));
+ return Void();
+ }
+
+ optional<vector<uint8_t>> readerPublicKey =
+ support::certificateChainGetTopMostKey(readerCertificateChain.value());
+ if (!readerPublicKey) {
+ _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+ "Unable to get public key from reader certificate chain"));
+ return Void();
+ }
+
+ const vector<uint8_t>& itemsRequestBytes = itemsRequest;
+ vector<uint8_t> dataThatWasSigned = cppbor::Array()
+ .add("ReaderAuthentication")
+ .add(sessionTranscriptItem_->clone())
+ .add(cppbor::Semantic(24, itemsRequestBytes))
+ .encode();
+ if (!support::coseCheckEcDsaSignature(readerSignature,
+ dataThatWasSigned, // detached content
+ readerPublicKey.value())) {
+ _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+ "readerSignature check failed"));
+ return Void();
+ }
+ }
+
+ // Here's where we would validate the passed-in |authToken| to assure ourselves
+ // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
+ //
+ // However this involves calculating the MAC. However this requires access
+ // to the key needed to a pre-shared key which we don't have...
+ //
+
+ // 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) {
+ const cppbor::Array* array = sessionTranscriptItem_->asArray();
+ if (array == nullptr || array->size() != 2) {
+ _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "SessionTranscript is not an array with two items"));
+ return Void();
+ }
+ const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
+ if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
+ _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "First item in SessionTranscript array is not a "
+ "semantic with value 24"));
+ return Void();
+ }
+ const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
+ if (encodedDE == nullptr) {
+ _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Child of semantic in first item in SessionTranscript "
+ "array is not a bstr"));
+ return Void();
+ }
+ const vector<uint8_t>& bytesDE = encodedDE->value();
+
+ auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+ if (!getXYSuccess) {
+ _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ "Error extracting X and Y from ePub"));
+ return Void();
+ }
+ if (sessionTranscript.size() > 0 &&
+ !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
+ memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
+ _hidl_cb(support::result(ResultCode::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)"));
+ return Void();
+ }
+ }
+
+ // 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
+ // credential, but there are three requirements that must be met to work with
+ // this HAL:
+ if (itemsRequest.size() > 0) {
+ // 1. The content must be a CBOR-encoded structure.
+ auto [item, _, message] = cppbor::parse(itemsRequest);
+ if (item == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+ "Error decoding CBOR in itemsRequest: %s", message.c_str()));
+ return Void();
+ }
+
+ // 2. The CBOR structure must be a map.
+ const cppbor::Map* map = item->asMap();
+ if (map == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+ "itemsRequest is not a CBOR map"));
+ return Void();
+ }
+
+ // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
+ // the example below.
+ //
+ // NameSpaces = {
+ // + NameSpace => DataElements ; Requested data elements for each NameSpace
+ // }
+ //
+ // NameSpace = tstr
+ //
+ // DataElements = {
+ // + DataElement => IntentToRetain
+ // }
+ //
+ // DataElement = tstr
+ // IntentToRetain = bool
+ //
+ // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
+ // through 3.:
+ //
+ // {
+ // 'docType' : 'org.iso.18013-5.2019',
+ // 'nameSpaces' : {
+ // 'org.iso.18013-5.2019' : {
+ // 'Last name' : false,
+ // 'Birth date' : false,
+ // 'First name' : false,
+ // 'Home address' : true
+ // },
+ // 'org.aamva.iso.18013-5.2019' : {
+ // 'Real Id' : false
+ // }
+ // }
+ // }
+ //
+ const cppbor::Map* nsMap = nullptr;
+ for (size_t n = 0; n < map->size(); n++) {
+ const auto& [keyItem, valueItem] = (*map)[n];
+ if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
+ valueItem->type() == cppbor::MAP) {
+ nsMap = valueItem->asMap();
+ break;
+ }
+ }
+ if (nsMap == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+ "No nameSpaces map in top-most map"));
+ return Void();
+ }
+
+ for (size_t n = 0; n < nsMap->size(); n++) {
+ auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
+ const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
+ const cppbor::Map* nsInnerMap = nsValueItem->asMap();
+ if (nsKey == nullptr || nsInnerMap == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+ "Type mismatch in nameSpaces map"));
+ return Void();
+ }
+ string requestedNamespace = nsKey->value();
+ vector<string> requestedKeys;
+ for (size_t m = 0; m < nsInnerMap->size(); m++) {
+ const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
+ const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
+ const cppbor::Simple* simple = innerMapValueItem->asSimple();
+ const cppbor::Bool* intentToRetainItem =
+ (simple != nullptr) ? simple->asBool() : nullptr;
+ if (nameItem == nullptr || intentToRetainItem == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+ "Type mismatch in value in nameSpaces map"));
+ return Void();
+ }
+ requestedKeys.push_back(nameItem->value());
+ }
+ requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
+ }
+ }
+
+ // Finally, validate all the access control profiles in the requestData.
+ bool haveAuthToken = (authToken.mac.size() > 0);
+ for (const auto& profile : accessControlProfiles) {
+ if (!support::secureAccessControlProfileCheckMac(profile, storageKey_)) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Error checking MAC for profile with id %d", int(profile.id)));
+ return Void();
+ }
+ ResultCode accessControlCheck = ResultCode::OK;
+ if (profile.userAuthenticationRequired) {
+ if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
+ accessControlCheck = ResultCode::USER_AUTHENTICATION_FAILED;
+ }
+ } else if (profile.readerCertificate.size() > 0) {
+ if (!readerCertificateChain ||
+ !checkReaderAuthentication(profile, readerCertificateChain.value())) {
+ accessControlCheck = ResultCode::READER_AUTHENTICATION_FAILED;
+ }
+ }
+ profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
+ }
+
+ deviceNameSpacesMap_ = cppbor::Map();
+ currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+ requestCountsRemaining_ = requestCounts;
+ currentNameSpace_ = "";
+
+ itemsRequest_ = itemsRequest;
+
+ numStartRetrievalCalls_ += 1;
+ _hidl_cb(support::resultOK());
+ return Void();
+}
+
+Return<void> IdentityCredential::startRetrieveEntryValue(
+ const hidl_string& nameSpace, const hidl_string& name, uint32_t entrySize,
+ const hidl_vec<uint16_t>& accessControlProfileIds, startRetrieveEntryValue_cb _hidl_cb) {
+ if (name.empty()) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Name cannot be empty"));
+ return Void();
+ }
+ if (nameSpace.empty()) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Name space cannot be empty"));
+ return Void();
+ }
+
+ if (requestCountsRemaining_.size() == 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "No more name spaces left to go through"));
+ return Void();
+ }
+
+ if (currentNameSpace_ == "") {
+ // First call.
+ currentNameSpace_ = nameSpace;
+ }
+
+ if (nameSpace == currentNameSpace_) {
+ // Same namespace.
+ if (requestCountsRemaining_[0] == 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "No more entries to be retrieved in current name space"));
+ return Void();
+ }
+ requestCountsRemaining_[0] -= 1;
+ } else {
+ // New namespace.
+ if (requestCountsRemaining_[0] != 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Moved to new name space but %d entries need to be retrieved "
+ "in current name space",
+ int(requestCountsRemaining_[0])));
+ return Void();
+ }
+ if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+ deviceNameSpacesMap_.add(currentNameSpace_,
+ std::move(currentNameSpaceDeviceNameSpacesMap_));
+ }
+ currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+ requestCountsRemaining_.erase(requestCountsRemaining_.begin());
+ currentNameSpace_ = nameSpace;
+ }
+
+ // It's permissible to have an empty itemsRequest... but if non-empty you can
+ // only request what was specified in said itemsRequest. Enforce that.
+ if (itemsRequest_.size() > 0) {
+ const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
+ if (it == requestedNameSpacesAndNames_.end()) {
+ _hidl_cb(support::result(ResultCode::NOT_IN_REQUEST_MESSAGE,
+ "Name space '%s' was not requested in startRetrieval",
+ nameSpace.c_str()));
+ return Void();
+ }
+ const auto& dataItemNames = it->second;
+ if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
+ _hidl_cb(support::result(
+ ResultCode::NOT_IN_REQUEST_MESSAGE,
+ "Data item name '%s' in name space '%s' was not requested in startRetrieval",
+ name.c_str(), nameSpace.c_str()));
+ return Void();
+ }
+ }
+
+ // Enforce access control.
+ //
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ ResultCode accessControl = ResultCode::NO_ACCESS_CONTROL_PROFILES;
+ for (auto id : accessControlProfileIds) {
+ auto search = profileIdToAccessCheckResult_.find(id);
+ if (search == profileIdToAccessCheckResult_.end()) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Requested entry with unvalidated profile id %d", (int(id))));
+ return Void();
+ }
+ ResultCode accessControlForProfile = search->second;
+ if (accessControlForProfile == ResultCode::OK) {
+ accessControl = ResultCode::OK;
+ break;
+ }
+ accessControl = accessControlForProfile;
+ }
+ if (accessControl != ResultCode::OK) {
+ _hidl_cb(support::result(accessControl, "Access control check failed"));
+ return Void();
+ }
+
+ entryAdditionalData_ =
+ support::entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+ currentName_ = name;
+ entryRemainingBytes_ = entrySize;
+ entryValue_.resize(0);
+
+ _hidl_cb(support::resultOK());
+ return Void();
+}
+
+Return<void> IdentityCredential::retrieveEntryValue(const hidl_vec<uint8_t>& encryptedContent,
+ retrieveEntryValue_cb _hidl_cb) {
+ optional<vector<uint8_t>> content =
+ support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+ if (!content) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Error decrypting data"), {});
+ return Void();
+ }
+
+ size_t chunkSize = content.value().size();
+
+ if (chunkSize > entryRemainingBytes_) {
+ LOG(ERROR) << "Retrieved chunk of size " << chunkSize
+ << " is bigger than remaining space of size " << entryRemainingBytes_;
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Retrieved chunk of size %zd is bigger than remaining space "
+ "of size %zd",
+ chunkSize, entryRemainingBytes_),
+ {});
+ return Void();
+ }
+
+ entryRemainingBytes_ -= chunkSize;
+ if (entryRemainingBytes_ > 0) {
+ if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Retrieved non-final chunk of size %zd but expected "
+ "kGcmChunkSize which is %zd",
+ chunkSize, IdentityCredentialStore::kGcmChunkSize),
+ {});
+ return Void();
+ }
+ }
+
+ entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
+
+ if (entryRemainingBytes_ == 0) {
+ auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
+ if (entryValueItem == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Retrieved data invalid CBOR"), {});
+ return Void();
+ }
+ currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
+ }
+
+ _hidl_cb(support::resultOK(), content.value());
+ return Void();
+}
+
+Return<void> IdentityCredential::finishRetrieval(const hidl_vec<uint8_t>& signingKeyBlob,
+ finishRetrieval_cb _hidl_cb) {
+ if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+ deviceNameSpacesMap_.add(currentNameSpace_,
+ std::move(currentNameSpaceDeviceNameSpacesMap_));
+ }
+ vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
+
+ // If there's no signing key or no sessionTranscript or no reader ephemeral
+ // public key, we return the empty MAC.
+ optional<vector<uint8_t>> mac;
+ if (signingKeyBlob.size() > 0 && sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0) {
+ cppbor::Array array;
+ array.add("DeviceAuthentication");
+ array.add(sessionTranscriptItem_->clone());
+ array.add(docType_);
+ array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
+ vector<uint8_t> encodedDeviceAuthentication = array.encode();
+
+ vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
+ optional<vector<uint8_t>> signingKey =
+ support::decryptAes128Gcm(storageKey_, signingKeyBlob, docTypeAsBlob);
+ if (!signingKey) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Error decrypting signingKeyBlob"),
+ {}, {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> sharedSecret =
+ support::ecdh(readerPublicKey_, signingKey.value());
+ if (!sharedSecret) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error doing ECDH"), {}, {});
+ return Void();
+ }
+
+ vector<uint8_t> salt = {0x00};
+ vector<uint8_t> info = {};
+ optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+ if (!derivedKey) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error deriving key from shared secret"),
+ {}, {});
+ return Void();
+ }
+
+ mac = support::coseMac0(derivedKey.value(), {}, // payload
+ encodedDeviceAuthentication); // additionalData
+ if (!mac) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error MACing data"), {}, {});
+ return Void();
+ }
+ }
+
+ _hidl_cb(support::resultOK(), mac.value_or(vector<uint8_t>({})), encodedDeviceNameSpaces);
+ return Void();
+}
+
+Return<void> IdentityCredential::generateSigningKeyPair(generateSigningKeyPair_cb _hidl_cb) {
+ string serialDecimal = "0"; // TODO: set serial to something unique
+ string issuer = "Android Open Source Project";
+ string subject = "Android IdentityCredential Reference Implementation";
+ time_t validityNotBefore = time(nullptr);
+ time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+ optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
+ if (!signingKeyPKCS8) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error creating signingKey"), {}, {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> signingPublicKey =
+ support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
+ if (!signingPublicKey) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting public part of signingKey"), {},
+ {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
+ if (!signingKey) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting private part of signingKey"),
+ {}, {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
+ signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+ if (!certificate) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error creating signingKey"), {}, {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> nonce = support::getRandom(12);
+ if (!nonce) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting random"), {}, {});
+ return Void();
+ }
+ vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
+ optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
+ storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
+ if (!encryptedSigningKey) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error encrypting signingKey"), {}, {});
+ return Void();
+ }
+ _hidl_cb(support::resultOK(), encryptedSigningKey.value(), certificate.value());
+ return Void();
+}
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
diff --git a/identity/1.0/default/IdentityCredential.h b/identity/1.0/default/IdentityCredential.h
new file mode 100644
index 0000000..eb8787b
--- /dev/null
+++ b/identity/1.0/default/IdentityCredential.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019, 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_IDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+
+#include <android/hardware/identity/1.0/IIdentityCredential.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor/cppbor.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::map;
+using ::std::string;
+using ::std::vector;
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+
+using MapStringToVectorOfStrings = map<string, vector<string>>;
+
+class IdentityCredential : public IIdentityCredential {
+ public:
+ IdentityCredential(const hidl_vec<uint8_t>& credentialData)
+ : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {}
+
+ // Parses and decrypts credentialData_, return false on failure. Must be
+ // called right after construction.
+ ResultCode initialize();
+
+ // Methods from ::android::hardware::identity::IIdentityCredential follow.
+
+ Return<void> deleteCredential(deleteCredential_cb _hidl_cb) override;
+ Return<void> createEphemeralKeyPair(createEphemeralKeyPair_cb _hidl_cb) override;
+
+ Return<void> setReaderEphemeralPublicKey(const hidl_vec<uint8_t>& publicKey,
+ setReaderEphemeralPublicKey_cb _hidl_cb) override;
+
+ Return<void> createAuthChallenge(createAuthChallenge_cb _hidl_cb) override;
+
+ Return<void> startRetrieval(const hidl_vec<SecureAccessControlProfile>& accessControlProfiles,
+ const HardwareAuthToken& authToken,
+ const hidl_vec<uint8_t>& itemsRequest,
+ const hidl_vec<uint8_t>& sessionTranscript,
+ const hidl_vec<uint8_t>& readerSignature,
+ const hidl_vec<uint16_t>& requestCounts,
+ startRetrieval_cb _hidl_cb) override;
+ Return<void> startRetrieveEntryValue(const hidl_string& nameSpace, const hidl_string& name,
+ uint32_t entrySize,
+ const hidl_vec<uint16_t>& accessControlProfileIds,
+ startRetrieveEntryValue_cb _hidl_cb) override;
+ Return<void> retrieveEntryValue(const hidl_vec<uint8_t>& encryptedContent,
+ retrieveEntryValue_cb _hidl_cb) override;
+ Return<void> finishRetrieval(const hidl_vec<uint8_t>& signingKeyBlob,
+ finishRetrieval_cb _hidl_cb) override;
+
+ Return<void> generateSigningKeyPair(generateSigningKeyPair_cb _hidl_cb) override;
+
+ private:
+ // Set by constructor
+ vector<uint8_t> credentialData_;
+ int numStartRetrievalCalls_;
+
+ // Set by initialize()
+ string docType_;
+ bool testCredential_;
+ vector<uint8_t> storageKey_;
+ vector<uint8_t> credentialPrivKey_;
+
+ // Set by createEphemeralKeyPair()
+ vector<uint8_t> ephemeralPublicKey_;
+
+ // Set by setReaderEphemeralPublicKey()
+ vector<uint8_t> readerPublicKey_;
+
+ // Set by createAuthChallenge()
+ uint64_t authChallenge_;
+
+ // Set at startRetrieval() time.
+ map<uint16_t, ResultCode> profileIdToAccessCheckResult_;
+ vector<uint8_t> sessionTranscript_;
+ std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
+ vector<uint8_t> itemsRequest_;
+ vector<uint16_t> requestCountsRemaining_;
+ MapStringToVectorOfStrings requestedNameSpacesAndNames_;
+ cppbor::Map deviceNameSpacesMap_;
+ cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
+
+ // Set at startRetrieveEntryValue() time.
+ string currentNameSpace_;
+ string currentName_;
+ size_t entryRemainingBytes_;
+ vector<uint8_t> entryValue_;
+ vector<uint8_t> entryAdditionalData_;
+};
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
diff --git a/identity/1.0/default/IdentityCredentialStore.cpp b/identity/1.0/default/IdentityCredentialStore.cpp
new file mode 100644
index 0000000..9eb1e70
--- /dev/null
+++ b/identity/1.0/default/IdentityCredentialStore.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019, 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 "IdentityCredentialStore"
+
+#include "IdentityCredentialStore.h"
+#include "IdentityCredential.h"
+#include "WritableIdentityCredential.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+// Methods from ::android::hardware::identity::IIdentityCredentialStore follow.
+
+Return<void> IdentityCredentialStore::getHardwareInformation(getHardwareInformation_cb _hidl_cb) {
+ _hidl_cb(support::resultOK(), "IdentityCredential Reference Implementation", "Google",
+ kGcmChunkSize, false /* isDirectAccess */, {} /* supportedDocTypes */);
+ return Void();
+}
+
+Return<void> IdentityCredentialStore::createCredential(const hidl_string& docType,
+ bool testCredential,
+ createCredential_cb _hidl_cb) {
+ auto writable_credential = new WritableIdentityCredential(docType, testCredential);
+ if (!writable_credential->initialize()) {
+ _hidl_cb(support::result(ResultCode::FAILED,
+ "Error initializing WritableIdentityCredential"),
+ writable_credential);
+ return Void();
+ }
+ _hidl_cb(support::resultOK(), writable_credential);
+ return Void();
+}
+
+Return<void> IdentityCredentialStore::getCredential(const hidl_vec<uint8_t>& credentialData,
+ getCredential_cb _hidl_cb) {
+ auto credential = new IdentityCredential(credentialData);
+ // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right now.
+ auto ret = credential->initialize();
+ if (ret != ResultCode::OK) {
+ _hidl_cb(support::result(ret, "Error initializing IdentityCredential"), credential);
+ return Void();
+ }
+ _hidl_cb(support::resultOK(), credential);
+ return Void();
+}
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
diff --git a/identity/1.0/default/IdentityCredentialStore.h b/identity/1.0/default/IdentityCredentialStore.h
new file mode 100644
index 0000000..ad75360
--- /dev/null
+++ b/identity/1.0/default/IdentityCredentialStore.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019, 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_IDENTITYCREDENTIALSTORE_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+
+class IdentityCredentialStore : public IIdentityCredentialStore {
+ public:
+ IdentityCredentialStore() {}
+
+ // The GCM chunk size used by this implementation is 64 KiB.
+ static constexpr size_t kGcmChunkSize = 64 * 1024;
+
+ // Methods from ::android::hardware::identity::IIdentityCredentialStore follow.
+ Return<void> getHardwareInformation(getHardwareInformation_cb _hidl_cb) override;
+ Return<void> createCredential(const hidl_string& docType, bool testCredential,
+ createCredential_cb _hidl_cb) override;
+ Return<void> getCredential(const hidl_vec<uint8_t>& credentialData,
+ getCredential_cb _hidl_cb) override;
+};
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
diff --git a/identity/1.0/default/OWNERS b/identity/1.0/default/OWNERS
new file mode 100644
index 0000000..6969910
--- /dev/null
+++ b/identity/1.0/default/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+zeuthen@google.com
diff --git a/identity/1.0/default/WritableIdentityCredential.cpp b/identity/1.0/default/WritableIdentityCredential.cpp
new file mode 100644
index 0000000..548b4c0
--- /dev/null
+++ b/identity/1.0/default/WritableIdentityCredential.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright 2019, 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 "WritableIdentityCredential"
+
+#include "WritableIdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor/cppbor.h>
+#include <cppbor/cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::optional;
+
+// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
+// |credentialPrivKey|.
+static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
+ const vector<uint8_t>& credentialPrivKey,
+ vector<uint8_t>& credentialKeys) {
+ if (storageKey.size() != 16) {
+ LOG(ERROR) << "Size of storageKey is not 16";
+ return false;
+ }
+
+ cppbor::Array array;
+ array.add(cppbor::Bstr(storageKey));
+ array.add(cppbor::Bstr(credentialPrivKey));
+ credentialKeys = array.encode();
+ return true;
+}
+
+// Writes CBOR-encoded structure to |credentialData| containing |docType|,
+// |testCredential| and |credentialKeys|. The latter element will be stored in
+// encrypted form, using |hardwareBoundKey| as the encryption key.
+bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
+ bool testCredential, const vector<uint8_t>& credentialKeys,
+ vector<uint8_t>& credentialData) {
+ optional<vector<uint8_t>> nonce = support::getRandom(12);
+ if (!nonce) {
+ LOG(ERROR) << "Error getting random";
+ return false;
+ }
+ vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
+ optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
+ hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
+ if (!credentialBlob) {
+ LOG(ERROR) << "Error encrypting CredentialKeys blob";
+ return false;
+ }
+
+ cppbor::Array array;
+ array.add(docType);
+ array.add(testCredential);
+ array.add(cppbor::Bstr(credentialBlob.value()));
+ credentialData = array.encode();
+ return true;
+}
+
+bool WritableIdentityCredential::initialize() {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ if (!keyPair) {
+ LOG(ERROR) << "Error creating credentialKey";
+ return false;
+ }
+
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ if (!pubKey) {
+ LOG(ERROR) << "Error getting public part of credentialKey";
+ return false;
+ }
+ credentialPubKey_ = pubKey.value();
+
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ if (!privKey) {
+ LOG(ERROR) << "Error getting private part of credentialKey";
+ return false;
+ }
+ credentialPrivKey_ = privKey.value();
+
+ optional<vector<uint8_t>> random = support::getRandom(16);
+ if (!random) {
+ LOG(ERROR) << "Error creating storageKey";
+ return false;
+ }
+ storageKey_ = random.value();
+
+ return true;
+}
+
+Return<void> WritableIdentityCredential::getAttestationCertificate(
+ const hidl_vec<uint8_t>& /* attestationChallenge */,
+ getAttestationCertificate_cb _hidl_cb) {
+ // For now, we dynamically generate an attestion key on each and every
+ // request and use that to sign CredentialKey. In a real implementation this
+ // would look very differently.
+ optional<vector<uint8_t>> attestationKeyPair = support::createEcKeyPair();
+ if (!attestationKeyPair) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error creating attestationKey"), {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> attestationPubKey =
+ support::ecKeyPairGetPublicKey(attestationKeyPair.value());
+ if (!attestationPubKey) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting public part of attestationKey"),
+ {});
+ return Void();
+ }
+
+ optional<vector<uint8_t>> attestationPrivKey =
+ support::ecKeyPairGetPrivateKey(attestationKeyPair.value());
+ if (!attestationPrivKey) {
+ _hidl_cb(
+ support::result(ResultCode::FAILED, "Error getting private part of attestationKey"),
+ {});
+ return Void();
+ }
+
+ string serialDecimal;
+ string issuer;
+ string subject;
+ time_t validityNotBefore = time(nullptr);
+ time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+ // First create a certificate for |credentialPubKey| which is signed by
+ // |attestationPrivKey|.
+ //
+ serialDecimal = "0"; // TODO: set serial to |attestationChallenge|
+ issuer = "Android Open Source Project";
+ subject = "Android IdentityCredential CredentialKey";
+ optional<vector<uint8_t>> credentialPubKeyCertificate = support::ecPublicKeyGenerateCertificate(
+ credentialPubKey_, attestationPrivKey.value(), serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+ if (!credentialPubKeyCertificate) {
+ _hidl_cb(support::result(ResultCode::FAILED,
+ "Error creating certificate for credentialPubKey"),
+ {});
+ return Void();
+ }
+
+ // This is followed by a certificate for |attestationPubKey| self-signed by
+ // |attestationPrivKey|.
+ serialDecimal = "0"; // TODO: set serial
+ issuer = "Android Open Source Project";
+ subject = "Android IdentityCredential AttestationKey";
+ optional<vector<uint8_t>> attestationKeyCertificate = support::ecPublicKeyGenerateCertificate(
+ attestationPubKey.value(), attestationPrivKey.value(), serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+ if (!attestationKeyCertificate) {
+ _hidl_cb(support::result(ResultCode::FAILED,
+ "Error creating certificate for attestationPubKey"),
+ {});
+ return Void();
+ }
+
+ // Concatenate the certificates to form the chain.
+ vector<uint8_t> certificateChain;
+ certificateChain.insert(certificateChain.end(), credentialPubKeyCertificate.value().begin(),
+ credentialPubKeyCertificate.value().end());
+ certificateChain.insert(certificateChain.end(), attestationKeyCertificate.value().begin(),
+ attestationKeyCertificate.value().end());
+
+ _hidl_cb(support::resultOK(), certificateChain);
+ return Void();
+}
+
+Return<void> WritableIdentityCredential::startPersonalization(uint16_t accessControlProfileCount,
+ const hidl_vec<uint16_t>& entryCounts,
+ startPersonalization_cb _hidl_cb) {
+ numAccessControlProfileRemaining_ = accessControlProfileCount;
+ remainingEntryCounts_ = entryCounts;
+ entryNameSpace_ = "";
+
+ signedDataAccessControlProfiles_ = cppbor::Array();
+ signedDataNamespaces_ = cppbor::Map();
+ signedDataCurrentNamespace_ = cppbor::Array();
+
+ _hidl_cb(support::resultOK());
+ return Void();
+}
+
+Return<void> WritableIdentityCredential::addAccessControlProfile(
+ uint16_t id, const hidl_vec<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId, addAccessControlProfile_cb _hidl_cb) {
+ SecureAccessControlProfile profile;
+
+ if (numAccessControlProfileRemaining_ == 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "numAccessControlProfileRemaining_ is 0 and expected non-zero"),
+ profile);
+ return Void();
+ }
+
+ // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
+ // be zero.
+ if (!userAuthenticationRequired && timeoutMillis != 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "userAuthenticationRequired is false but timeout is non-zero"),
+ profile);
+ return Void();
+ }
+
+ profile.id = id;
+ profile.readerCertificate = readerCertificate;
+ profile.userAuthenticationRequired = userAuthenticationRequired;
+ profile.timeoutMillis = timeoutMillis;
+ profile.secureUserId = secureUserId;
+ optional<vector<uint8_t>> mac =
+ support::secureAccessControlProfileCalcMac(profile, storageKey_);
+ if (!mac) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error calculating MAC for profile"), profile);
+ return Void();
+ }
+ profile.mac = mac.value();
+
+ cppbor::Map profileMap;
+ profileMap.add("id", profile.id);
+ if (profile.readerCertificate.size() > 0) {
+ profileMap.add("readerCertificate", cppbor::Bstr(profile.readerCertificate));
+ }
+ if (profile.userAuthenticationRequired) {
+ profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired);
+ profileMap.add("timeoutMillis", profile.timeoutMillis);
+ }
+ signedDataAccessControlProfiles_.add(std::move(profileMap));
+
+ numAccessControlProfileRemaining_--;
+
+ _hidl_cb(support::resultOK(), profile);
+ return Void();
+}
+
+Return<void> WritableIdentityCredential::beginAddEntry(
+ const hidl_vec<uint16_t>& accessControlProfileIds, const hidl_string& nameSpace,
+ const hidl_string& name, uint32_t entrySize, beginAddEntry_cb _hidl_cb) {
+ if (numAccessControlProfileRemaining_ != 0) {
+ LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_
+ << " and expected zero";
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "numAccessControlProfileRemaining_ is %zd and expected zero",
+ numAccessControlProfileRemaining_));
+ return Void();
+ }
+
+ if (remainingEntryCounts_.size() == 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "No more namespaces to add to"));
+ return Void();
+ }
+
+ // Handle initial beginEntry() call.
+ if (entryNameSpace_ == "") {
+ entryNameSpace_ = nameSpace;
+ }
+
+ // If the namespace changed...
+ if (nameSpace != entryNameSpace_) {
+ // Then check that all entries in the previous namespace have been added..
+ if (remainingEntryCounts_[0] != 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "New namespace but %d entries remain to be added",
+ int(remainingEntryCounts_[0])));
+ return Void();
+ }
+ remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+
+ if (signedDataCurrentNamespace_.size() > 0) {
+ signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
+ signedDataCurrentNamespace_ = cppbor::Array();
+ }
+ } else {
+ // Same namespace...
+ if (remainingEntryCounts_[0] == 0) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Same namespace but no entries remain to be added"));
+ return Void();
+ }
+ remainingEntryCounts_[0] -= 1;
+ }
+
+ entryAdditionalData_ =
+ support::entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+ entryRemainingBytes_ = entrySize;
+ entryNameSpace_ = nameSpace;
+ entryName_ = name;
+ entryAccessControlProfileIds_ = accessControlProfileIds;
+ entryBytes_.resize(0);
+ // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+ _hidl_cb(support::resultOK());
+ return Void();
+}
+
+Return<void> WritableIdentityCredential::addEntryValue(const hidl_vec<uint8_t>& content,
+ addEntryValue_cb _hidl_cb) {
+ size_t contentSize = content.size();
+
+ if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
+ _hidl_cb(support::result(
+ ResultCode::INVALID_DATA,
+ "Passed in chunk of size %zd is bigger than kGcmChunkSize which is %zd",
+ contentSize, IdentityCredentialStore::kGcmChunkSize),
+ {});
+ return Void();
+ }
+ if (contentSize > entryRemainingBytes_) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Passed in chunk of size %zd is bigger than remaining space "
+ "of size %zd",
+ contentSize, entryRemainingBytes_),
+ {});
+ return Void();
+ }
+
+ entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
+ entryRemainingBytes_ -= contentSize;
+ if (entryRemainingBytes_ > 0) {
+ if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA,
+ "Retrieved non-final chunk of size %zd but expected "
+ "kGcmChunkSize which is %zd",
+ contentSize, IdentityCredentialStore::kGcmChunkSize),
+ {});
+ return Void();
+ }
+ }
+
+ optional<vector<uint8_t>> nonce = support::getRandom(12);
+ if (!nonce) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error getting nonce"), {});
+ return Void();
+ }
+ optional<vector<uint8_t>> encryptedContent =
+ support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+ if (!encryptedContent) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error encrypting content"), {});
+ return Void();
+ }
+
+ if (entryRemainingBytes_ == 0) {
+ // TODO: ideally do do this without parsing the data (but still validate data is valid
+ // CBOR).
+ auto [item, _, message] = cppbor::parse(entryBytes_);
+ if (item == nullptr) {
+ _hidl_cb(support::result(ResultCode::INVALID_DATA, "Data is not valid CBOR"), {});
+ return Void();
+ }
+ cppbor::Map entryMap;
+ entryMap.add("name", entryName_);
+ entryMap.add("value", std::move(item));
+ cppbor::Array profileIdArray;
+ for (auto id : entryAccessControlProfileIds_) {
+ profileIdArray.add(id);
+ }
+ entryMap.add("accessControlProfiles", std::move(profileIdArray));
+ signedDataCurrentNamespace_.add(std::move(entryMap));
+ }
+
+ _hidl_cb(support::resultOK(), encryptedContent.value());
+ return Void();
+}
+
+Return<void> WritableIdentityCredential::finishAddingEntries(finishAddingEntries_cb _hidl_cb) {
+ if (signedDataCurrentNamespace_.size() > 0) {
+ signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
+ }
+ cppbor::Array popArray;
+ popArray.add("ProofOfProvisioning")
+ .add(docType_)
+ .add(std::move(signedDataAccessControlProfiles_))
+ .add(std::move(signedDataNamespaces_))
+ .add(testCredential_);
+ vector<uint8_t> encodedCbor = popArray.encode();
+
+ optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
+ encodedCbor, // payload
+ {}, // additionalData
+ {}); // certificateChain
+ if (!signature) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error signing data"), {}, {});
+ return Void();
+ }
+
+ vector<uint8_t> credentialKeys;
+ if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error generating CredentialKeys"), {}, {});
+ return Void();
+ }
+
+ vector<uint8_t> credentialData;
+ if (!generateCredentialData(testCredential_ ? support::getTestHardwareBoundKey()
+ : support::getHardwareBoundKey(),
+ docType_, testCredential_, credentialKeys, credentialData)) {
+ _hidl_cb(support::result(ResultCode::FAILED, "Error generating CredentialData"), {}, {});
+ return Void();
+ }
+
+ _hidl_cb(support::resultOK(), credentialData, signature.value());
+ return Void();
+}
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
diff --git a/identity/1.0/default/WritableIdentityCredential.h b/identity/1.0/default/WritableIdentityCredential.h
new file mode 100644
index 0000000..9f4e303
--- /dev/null
+++ b/identity/1.0/default/WritableIdentityCredential.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019, 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_WRITABLEIDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+
+#include <android/hardware/identity/1.0/IWritableIdentityCredential.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::string;
+using ::std::vector;
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+class WritableIdentityCredential : public IWritableIdentityCredential {
+ public:
+ WritableIdentityCredential(const hidl_string& docType, bool testCredential)
+ : docType_(docType), testCredential_(testCredential) {}
+
+ // Creates the Credential Key. Returns false on failure. Must be called
+ // right after construction.
+ bool initialize();
+
+ // Methods from ::android::hardware::identity::IWritableIdentityCredential
+ // follow.
+ Return<void> getAttestationCertificate(const hidl_vec<uint8_t>& attestationChallenge,
+ getAttestationCertificate_cb _hidl_cb) override;
+
+ Return<void> startPersonalization(uint16_t accessControlProfileCount,
+ const hidl_vec<uint16_t>& entryCounts,
+ startPersonalization_cb _hidl_cb) override;
+
+ Return<void> addAccessControlProfile(uint16_t id, const hidl_vec<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId,
+ addAccessControlProfile_cb _hidl_cb) override;
+
+ Return<void> beginAddEntry(const hidl_vec<uint16_t>& accessControlProfileIds,
+ const hidl_string& nameSpace, const hidl_string& name,
+ uint32_t entrySize, beginAddEntry_cb _hidl_cb) override;
+
+ Return<void> addEntryValue(const hidl_vec<uint8_t>& content,
+ addEntryValue_cb _hidl_cb) override;
+
+ Return<void> finishAddingEntries(finishAddingEntries_cb _hidl_cb) override;
+
+ private:
+ string docType_;
+ bool testCredential_;
+
+ // These are set in initialize().
+ vector<uint8_t> storageKey_;
+ vector<uint8_t> credentialPrivKey_;
+ vector<uint8_t> credentialPubKey_;
+
+ // These fields are initialized during startPersonalization()
+ size_t numAccessControlProfileRemaining_;
+ vector<uint16_t> remainingEntryCounts_;
+ cppbor::Array signedDataAccessControlProfiles_;
+ cppbor::Map signedDataNamespaces_;
+ cppbor::Array signedDataCurrentNamespace_;
+
+ // These fields are initialized during beginAddEntry()
+ size_t entryRemainingBytes_;
+ vector<uint8_t> entryAdditionalData_;
+ string entryNameSpace_;
+ string entryName_;
+ vector<uint16_t> entryAccessControlProfileIds_;
+ vector<uint8_t> entryBytes_;
+};
+
+} // namespace implementation
+} // namespace identity
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
diff --git a/identity/1.0/default/android.hardware.identity@1.0-service.example.rc b/identity/1.0/default/android.hardware.identity@1.0-service.example.rc
new file mode 100644
index 0000000..1eb7319
--- /dev/null
+++ b/identity/1.0/default/android.hardware.identity@1.0-service.example.rc
@@ -0,0 +1,3 @@
+service vendor.identity-1-0 /vendor/bin/hw/android.hardware.identity@1.0-service.example
+ class hal
+ user nobody
diff --git a/identity/1.0/default/service.cpp b/identity/1.0/default/service.cpp
new file mode 100644
index 0000000..839e803
--- /dev/null
+++ b/identity/1.0/default/service.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019, 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 "android.hardware.identity@1.0-service"
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include "IdentityCredentialStore.h"
+
+using android::hardware::joinRpcThreadpool;
+using android::hardware::identity::implementation::IdentityCredentialStore;
+
+int main(int /* argc */, char* argv[]) {
+ ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/);
+
+ ::android::base::InitLogging(argv, &android::base::StderrLogger);
+
+ auto identity_store = new IdentityCredentialStore();
+ auto status = identity_store->registerAsService();
+ if (status != android::OK) {
+ LOG(FATAL) << "Could not register service for IdentityCredentialStore 1.0 (" << status
+ << ")";
+ }
+ joinRpcThreadpool();
+ return -1; // Should never get here.
+}
diff --git a/identity/1.0/types.hal b/identity/1.0/types.hal
new file mode 100644
index 0000000..5aedfea
--- /dev/null
+++ b/identity/1.0/types.hal
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2018 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@1.0;
+
+/**
+ * The ResultCode enumeration is used to convey the status of an operation.
+ */
+enum ResultCode : int32_t {
+ /**
+ * Success.
+ */
+ OK = 0,
+
+ /**
+ * The operation failed. This is used as a generic catch-all for errors that don't belong
+ * in other categories, including memory/resource allocation failures and I/O errors.
+ */
+ FAILED = 1,
+
+ /**
+ * The passed data was invalid. This is a generic catch all for errors that don't belong
+ * in other categories related to parameter validation.
+ */
+ INVALID_DATA = 2,
+
+ /**
+ * The authToken parameter passed to IIdentityCredential.startRetrieval() is not valid.
+ */
+ INVALID_AUTH_TOKEN = 3,
+
+ /**
+ * The itemsRequest parameter passed to IIdentityCredential.startRetrieval() does not meet
+ * the requirements described in the documentation for that method.
+ */
+ INVALID_ITEMS_REQUEST_MESSAGE = 4,
+
+ /**
+ * The readerSignature parameter in IIdentityCredential.startRetrieval() is invalid,
+ * doesn't contain an embedded certificate chain, or the signature failed to
+ * validate.
+ */
+ READER_SIGNATURE_CHECK_FAILED = 5,
+
+ /**
+ * The sessionTranscript passed to startRetrieval() did not contain the ephmeral public
+ * key returned by createEphemeralPublicKey().
+ */
+ EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 6,
+
+ /**
+ * An access condition related to user authentication was not satisfied.
+ */
+ USER_AUTHENTICATION_FAILED = 7,
+
+ /**
+ * An access condition related to reader authentication was not satisfied.
+ */
+ READER_AUTHENTICATION_FAILED = 8,
+
+ /**
+ * The request data element has no access control profiles associated so it cannot be accessed.
+ */
+ NO_ACCESS_CONTROL_PROFILES = 9,
+
+ /**
+ * The requested data element is not in the provided non-empty itemsRequest message.
+ */
+ NOT_IN_REQUEST_MESSAGE = 10,
+
+ /**
+ * The passed-in sessionTranscript doesn't match the previously passed-in sessionTranscript.
+ */
+ SESSION_TRANSCRIPT_MISMATCH = 11,
+};
+
+/**
+ * A result has a ResultCode and corresponding textual message.
+ */
+struct Result {
+ /**
+ * The result code.
+ *
+ * Implementations must not use values not defined in the ResultCode enumeration.
+ */
+ ResultCode code;
+
+ /**
+ * A human-readable message in English conveying more detail about a failure.
+ *
+ * If code is ResultCode::OK this field must be set to the empty string.
+ */
+ string message;
+};
+
+struct SecureAccessControlProfile {
+ /**
+ * id is a numeric identifier that must be unique within the context of a Credential and may be
+ * used to reference the profile.
+ */
+ uint16_t id;
+
+ /**
+ * readerCertificate, if non-empty, specifies a single X.509 certificate (not a chain
+ * of certificates) that must be used to authenticate requests. For details about how
+ * this is done, see the readerSignature paremter of IIdentityCredential.startRetrieval.
+ */
+ vec<uint8_t> readerCertificate;
+
+ /**
+ * if true, the user is required to authenticate to allow requests. Required authentication
+ * fressness is specified by timeout below.
+ *
+ */
+ bool userAuthenticationRequired;
+
+ /**
+ * Timeout specifies the amount of time, in milliseconds, for which a user authentication (see
+ * above) is valid, if userAuthenticationRequired is set to true. If userAuthenticationRequired
+ * is true and timout is zero then authentication is required for each reader session.
+ *
+ * If userAuthenticationRequired is false, timeout must be zero.
+ */
+ uint64_t timeoutMillis;
+
+ /**
+ * secureUserId must be non-zero if userAuthenticationRequired is true.
+ * It is not related to any Android user ID or UID, but is created in the
+ * Gatekeeper application in the secure environment.
+ */
+ uint64_t secureUserId;
+
+ /**
+ * The mac is used to authenticate the access control profile. It contains:
+ *
+ * AES-GCM-ENC(storageKey, R, {}, AccessControlProfile)
+ *
+ * where AccessControlProfile is the CBOR map:
+ *
+ * AccessControlProfile = {
+ * "id": uint,
+ * ? "readerCertificate" : bstr,
+ * ? (
+ * "userAuthenticationRequired" : bool,
+ * "timeoutMillis" : uint,
+ * "secureUserId" : uint
+ * )
+ * }
+ */
+ vec<uint8_t> mac;
+};
diff --git a/identity/1.0/vts/OWNERS b/identity/1.0/vts/OWNERS
new file mode 100644
index 0000000..6969910
--- /dev/null
+++ b/identity/1.0/vts/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+zeuthen@google.com
diff --git a/identity/1.0/vts/functional/Android.bp b/identity/1.0/vts/functional/Android.bp
new file mode 100644
index 0000000..03b49de
--- /dev/null
+++ b/identity/1.0/vts/functional/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2019 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.
+//
+
+cc_test {
+ name: "VtsHalIdentityCredentialTargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: [
+ "VtsHalIdentityCredentialTargetTest.cpp",
+ ],
+ static_libs: [
+ "android.hardware.identity@1.0",
+ "android.hardware.identity-support-lib",
+ "android.hardware.keymaster@4.0",
+ "libcppbor",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts-core",
+ ],
+}
diff --git a/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp b/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp
new file mode 100644
index 0000000..903e912
--- /dev/null
+++ b/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2019 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 "IdentityCredentialHidlHalTest"
+
+#include <map>
+
+#include <android-base/logging.h>
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace test {
+
+using ::android::hardware::identity::V1_0::IIdentityCredential;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+
+// ---------------------------------------------------------------------------
+// Test Data.
+// ---------------------------------------------------------------------------
+
+struct TestEntryData {
+ TestEntryData(string nameSpace, string name, vector<uint16_t> profileIds)
+ : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+ TestEntryData(string nameSpace, string name, const string& value, vector<uint16_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+ }
+ TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+ vector<uint16_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bstr(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, bool value, vector<uint16_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bool(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, int64_t value, vector<uint16_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ if (value >= 0) {
+ valueCbor = cppbor::Uint(value).encode();
+ } else {
+ valueCbor = cppbor::Nint(-value).encode();
+ }
+ }
+
+ string nameSpace;
+ string name;
+ vector<uint8_t> valueCbor;
+ vector<uint16_t> profileIds;
+};
+
+struct TestProfile {
+ uint16_t id;
+ hidl_vec<uint8_t> readerCertificate;
+ bool userAuthenticationRequired;
+ uint64_t timeoutMillis;
+};
+
+/************************************
+ * TEST DATA FOR AUTHENTICATION
+ ************************************/
+// Test authentication token for user authentication
+
+class IdentityCredentialStoreHidlTest : public ::testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ string serviceName = GetParam();
+ ASSERT_FALSE(serviceName.empty());
+ credentialStore_ = IIdentityCredentialStore::getService(serviceName);
+ ASSERT_NE(credentialStore_, nullptr);
+
+ credentialStore_->getHardwareInformation(
+ [&](const Result& result, const hidl_string& credentialStoreName,
+ const hidl_string& credentialStoreAuthorName, uint32_t chunkSize,
+ bool /* isDirectAccess */,
+ const hidl_vec<hidl_string> /* supportedDocTypes */) {
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_GT(credentialStoreName.size(), 0u);
+ ASSERT_GT(credentialStoreAuthorName.size(), 0u);
+ ASSERT_GE(chunkSize, 256u); // Chunk sizes < APDU buffer won't be supported
+ dataChunkSize_ = chunkSize;
+ });
+ }
+ virtual void TearDown() override {}
+
+ uint32_t dataChunkSize_ = 0;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityCredentialStoreHidlTest, HardwareConfiguration) {
+ credentialStore_->getHardwareInformation(
+ [&](const Result& result, const hidl_string& credentialStoreName,
+ const hidl_string& credentialStoreAuthorName, uint32_t chunkSize,
+ bool /* isDirectAccess */, const hidl_vec<hidl_string> /* supportedDocTypes */) {
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_GT(credentialStoreName.size(), 0u);
+ ASSERT_GT(credentialStoreAuthorName.size(), 0u);
+ ASSERT_GE(chunkSize, 256u); // Chunk sizes < APDU buffer won't be supported
+ });
+}
+
+TEST_P(IdentityCredentialStoreHidlTest, createAndRetrieveCredential) {
+ // First, generate a key-pair for the reader since its public key will be
+ // part of the request data.
+ optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+ ASSERT_TRUE(readerKeyPKCS8);
+ optional<vector<uint8_t>> readerPublicKey =
+ support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+ optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+ string serialDecimal = "1234";
+ string issuer = "Android Open Source Project";
+ string subject = "Android IdentityCredential VTS Test";
+ time_t validityNotBefore = time(nullptr);
+ time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+ optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
+ readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+ ASSERT_TRUE(readerCertificate);
+
+ // Make the portrait image really big (just shy of 256 KiB) to ensure that
+ // the chunking code gets exercised.
+ vector<uint8_t> portraitImage;
+ portraitImage.resize(256 * 1024 - 10);
+ for (size_t n = 0; n < portraitImage.size(); n++) {
+ portraitImage[n] = (uint8_t)n;
+ }
+
+ // Access control profiles:
+ const vector<TestProfile> testProfiles = {// Profile 0 (reader authentication)
+ {0, readerCertificate.value(), false, 0},
+ // Profile 1 (no authentication)
+ {1, {}, false, 0}};
+
+ HardwareAuthToken authToken = {};
+
+ // Here's the actual test data:
+ const vector<TestEntryData> testEntries = {
+ {"PersonalData", "Last name", string("Turing"), vector<uint16_t>{0, 1}},
+ {"PersonalData", "Birth date", string("19120623"), vector<uint16_t>{0, 1}},
+ {"PersonalData", "First name", string("Alan"), vector<uint16_t>{0, 1}},
+ {"PersonalData", "Home address", string("Maida Vale, London, England"),
+ vector<uint16_t>{0}},
+ {"Image", "Portrait image", portraitImage, vector<uint16_t>{0, 1}},
+ };
+ const vector<uint16_t> testEntriesEntryCounts = {static_cast<uint16_t>(testEntries.size() - 1),
+ 1u};
+
+ string cborPretty;
+ sp<IWritableIdentityCredential> writableCredential;
+
+ hidl_vec<uint8_t> empty{0};
+
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ Result result;
+ credentialStore_->createCredential(
+ docType, testCredential,
+ [&](const Result& _result, const sp<IWritableIdentityCredential>& _writableCredential) {
+ result = _result;
+ writableCredential = _writableCredential;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_NE(writableCredential, nullptr);
+
+ string challenge = "attestationChallenge";
+ vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+ vector<uint8_t> attestationCertificate;
+ writableCredential->getAttestationCertificate(
+ attestationChallenge,
+ [&](const Result& _result, const hidl_vec<uint8_t>& _attestationCertificate) {
+ result = _result;
+ attestationCertificate = _attestationCertificate;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts,
+ [&](const Result& _result) { result = _result; });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ vector<SecureAccessControlProfile> returnedSecureProfiles;
+ for (const auto& testProfile : testProfiles) {
+ SecureAccessControlProfile profile;
+ writableCredential->addAccessControlProfile(
+ testProfile.id, testProfile.readerCertificate,
+ testProfile.userAuthenticationRequired, testProfile.timeoutMillis,
+ 0, // secureUserId
+ [&](const Result& _result, const SecureAccessControlProfile& _profile) {
+ result = _result;
+ profile = _profile;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_EQ(testProfile.id, profile.id);
+ ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate);
+ ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+ ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+ ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+ returnedSecureProfiles.push_back(profile);
+ }
+
+ // Uses TestEntryData* pointer as key and values are the encrypted blobs. This
+ // is a little hacky but it works well enough.
+ map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+
+ for (const auto& entry : testEntries) {
+ vector<vector<uint8_t>> chunks = support::chunkVector(entry.valueCbor, dataChunkSize_);
+
+ writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+ entry.valueCbor.size(),
+ [&](const Result& _result) { result = _result; });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ vector<vector<uint8_t>> encryptedChunks;
+ for (const auto& chunk : chunks) {
+ writableCredential->addEntryValue(
+ chunk, [&](const Result& result, hidl_vec<uint8_t> encryptedContent) {
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_GT(encryptedContent.size(), 0u);
+ encryptedChunks.push_back(encryptedContent);
+ });
+ }
+ encryptedBlobs[&entry] = encryptedChunks;
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ writableCredential->finishAddingEntries(
+ [&](const Result& _result, const hidl_vec<uint8_t>& _credentialData,
+ const hidl_vec<uint8_t>& _proofOfProvisioningSignature) {
+ result = _result;
+ credentialData = _credentialData;
+ proofOfProvisioningSignature = _proofOfProvisioningSignature;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 0,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " },\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'PersonalData' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Birth date',\n"
+ " 'value' : '19120623',\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'First name',\n"
+ " 'value' : 'Alan',\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Home address',\n"
+ " 'value' : 'Maida Vale, London, England',\n"
+ " 'accessControlProfiles' : [0, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Image' : [\n"
+ " {\n"
+ " 'name' : 'Portrait image',\n"
+ " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+
+ optional<vector<uint8_t>> credentialPubKey =
+ support::certificateChainGetTopMostKey(attestationCertificate);
+ ASSERT_TRUE(credentialPubKey);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey.value()));
+ writableCredential = nullptr;
+
+ // Now that the credential has been provisioned, read it back and check the
+ // correct data is returned.
+ sp<IIdentityCredential> credential;
+ credentialStore_->getCredential(
+ credentialData, [&](const Result& _result, const sp<IIdentityCredential>& _credential) {
+ result = _result;
+ credential = _credential;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ ASSERT_NE(credential, nullptr);
+
+ optional<vector<uint8_t>> readerEphemeralKeyPair = support::createEcKeyPair();
+ ASSERT_TRUE(readerEphemeralKeyPair);
+ optional<vector<uint8_t>> readerEphemeralPublicKey =
+ support::ecKeyPairGetPublicKey(readerEphemeralKeyPair.value());
+ credential->setReaderEphemeralPublicKey(readerEphemeralPublicKey.value(),
+ [&](const Result& _result) { result = _result; });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ vector<uint8_t> ephemeralKeyPair;
+ credential->createEphemeralKeyPair(
+ [&](const Result& _result, const hidl_vec<uint8_t>& _ephemeralKeyPair) {
+ result = _result;
+ ephemeralKeyPair = _ephemeralKeyPair;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ optional<vector<uint8_t>> ephemeralPublicKey = support::ecKeyPairGetPublicKey(ephemeralKeyPair);
+
+ // Calculate requestData field and sign it with the reader key.
+ auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ephemeralPublicKey.value());
+ ASSERT_TRUE(getXYSuccess);
+ cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
+ vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+ vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+ cppbor::Array sessionTranscript = cppbor::Array()
+ .add(cppbor::Semantic(24, deviceEngagementBytes))
+ .add(cppbor::Semantic(24, eReaderPubBytes));
+ vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
+
+ vector<uint8_t> itemsRequestBytes =
+ cppbor::Map("nameSpaces",
+ cppbor::Map()
+ .add("PersonalData", cppbor::Map()
+ .add("Last name", false)
+ .add("Birth date", false)
+ .add("First name", false)
+ .add("Home address", true))
+ .add("Image", cppbor::Map().add("Portrait image", false)))
+ .encode();
+ cborPretty = support::cborPrettyPrint(itemsRequestBytes, 32, {"EphemeralPublicKey"});
+ EXPECT_EQ(
+ "{\n"
+ " 'nameSpaces' : {\n"
+ " 'PersonalData' : {\n"
+ " 'Last name' : false,\n"
+ " 'Birth date' : false,\n"
+ " 'First name' : false,\n"
+ " 'Home address' : true,\n"
+ " },\n"
+ " 'Image' : {\n"
+ " 'Portrait image' : false,\n"
+ " },\n"
+ " },\n"
+ "}",
+ cborPretty);
+ vector<uint8_t> dataToSign = cppbor::Array()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript.clone())
+ .add(cppbor::Semantic(24, itemsRequestBytes))
+ .encode();
+ optional<vector<uint8_t>> readerSignature =
+ support::coseSignEcDsa(readerKey.value(), {}, // content
+ dataToSign, // detached content
+ readerCertificate.value());
+ ASSERT_TRUE(readerSignature);
+
+ credential->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+ sessionTranscriptBytes, readerSignature.value(),
+ testEntriesEntryCounts,
+ [&](const Result& _result) { result = _result; });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ for (const auto& entry : testEntries) {
+ credential->startRetrieveEntryValue(entry.nameSpace, entry.name, entry.valueCbor.size(),
+ entry.profileIds,
+ [&](const Result& _result) { result = _result; });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ auto it = encryptedBlobs.find(&entry);
+ ASSERT_NE(it, encryptedBlobs.end());
+ const vector<vector<uint8_t>>& encryptedChunks = it->second;
+
+ vector<uint8_t> content;
+ for (const auto& encryptedChunk : encryptedChunks) {
+ vector<uint8_t> chunk;
+ credential->retrieveEntryValue(
+ encryptedChunk, [&](const Result& _result, const hidl_vec<uint8_t>& _chunk) {
+ result = _result;
+ chunk = _chunk;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ content.insert(content.end(), chunk.begin(), chunk.end());
+ }
+ EXPECT_EQ(content, entry.valueCbor);
+ }
+
+ // Generate the key that will be used to sign AuthenticatedData.
+ vector<uint8_t> signingKeyBlob;
+ vector<uint8_t> signingKeyCertificate;
+ credential->generateSigningKeyPair([&](const Result& _result,
+ const hidl_vec<uint8_t> _signingKeyBlob,
+ const hidl_vec<uint8_t> _signingKeyCertificate) {
+ result = _result;
+ signingKeyBlob = _signingKeyBlob;
+ signingKeyCertificate = _signingKeyCertificate;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+
+ vector<uint8_t> mac;
+ vector<uint8_t> deviceNameSpacesBytes;
+ credential->finishRetrieval(signingKeyBlob,
+ [&](const Result& _result, const hidl_vec<uint8_t> _mac,
+ const hidl_vec<uint8_t> _deviceNameSpacesBytes) {
+ result = _result;
+ mac = _mac;
+ deviceNameSpacesBytes = _deviceNameSpacesBytes;
+ });
+ EXPECT_EQ("", result.message);
+ ASSERT_EQ(ResultCode::OK, result.code);
+ cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 32, {});
+ ASSERT_EQ(
+ "{\n"
+ " 'PersonalData' : {\n"
+ " 'Last name' : 'Turing',\n"
+ " 'Birth date' : '19120623',\n"
+ " 'First name' : 'Alan',\n"
+ " 'Home address' : 'Maida Vale, London, England',\n"
+ " },\n"
+ " 'Image' : {\n"
+ " 'Portrait image' : <bstr size=262134 "
+ "sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " },\n"
+ "}",
+ cborPretty);
+ // The data that is MACed is ["DeviceAuthentication", sessionTranscriptBytes, docType,
+ // deviceNameSpacesBytes] so build up that structure
+ cppbor::Array deviceAuthentication;
+ deviceAuthentication.add("DeviceAuthentication");
+ deviceAuthentication.add(sessionTranscript.clone());
+ deviceAuthentication.add(docType);
+ deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
+ vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
+ optional<vector<uint8_t>> signingPublicKey =
+ support::certificateChainGetTopMostKey(signingKeyCertificate);
+ EXPECT_TRUE(signingPublicKey);
+
+ // Derive the key used for MACing.
+ optional<vector<uint8_t>> readerEphemeralPrivateKey =
+ support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
+ optional<vector<uint8_t>> sharedSecret =
+ support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value());
+ ASSERT_TRUE(sharedSecret);
+ vector<uint8_t> salt = {0x00};
+ vector<uint8_t> info = {};
+ optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+ ASSERT_TRUE(derivedKey);
+ optional<vector<uint8_t>> calculatedMac =
+ support::coseMac0(derivedKey.value(), {}, // payload
+ encodedDeviceAuthentication); // detached content
+ ASSERT_TRUE(calculatedMac);
+ EXPECT_EQ(mac, calculatedMac);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, IdentityCredentialStoreHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ IIdentityCredentialStore::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+
+} // namespace test
+} // namespace identity
+} // namespace hardware
+} // namespace android
diff --git a/identity/support/Android.bp b/identity/support/Android.bp
new file mode 100644
index 0000000..38dc10b
--- /dev/null
+++ b/identity/support/Android.bp
@@ -0,0 +1,103 @@
+// Copyright (C) 2019 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.
+//
+
+cc_library {
+ name: "android.hardware.identity-support-lib",
+ vendor_available: true,
+ srcs: [
+ "src/IdentityCredentialSupport.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ shared_libs: [
+ "android.hardware.identity@1.0",
+ "libcrypto",
+ "libbase",
+ "libhidlbase",
+ "libhardware",
+ ],
+ static_libs: [
+ "libcppbor",
+ ],
+}
+
+cc_test {
+ name: "android.hardware.identity-support-lib-test",
+ srcs: [
+ "tests/IdentityCredentialSupportTest.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.identity-support-lib",
+ "android.hardware.identity@1.0",
+ "libcrypto",
+ "libbase",
+ "libhidlbase",
+ "libhardware",
+ ],
+ static_libs: [
+ "libcppbor",
+ "libgmock",
+ ],
+ test_suites: ["general-tests"],
+}
+
+// --
+
+cc_library {
+ name: "libcppbor",
+ vendor_available: true,
+ host_supported: true,
+ srcs: [
+ "src/cppbor.cpp",
+ "src/cppbor_parse.cpp",
+ ],
+ export_include_dirs: [
+ "include/cppbor",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+cc_test {
+ name: "cppbor_test",
+ srcs: [
+ "tests/cppbor_test.cpp",
+ ],
+ shared_libs: [
+ "libcppbor",
+ "libbase",
+ ],
+ static_libs: [
+ "libgmock",
+ ],
+ test_suites: ["general-tests"],
+}
+
+cc_test_host {
+ name: "cppbor_host_test",
+ srcs: [
+ "tests/cppbor_test.cpp",
+ ],
+ shared_libs: [
+ "libcppbor",
+ "libbase",
+ ],
+ static_libs: [
+ "libgmock",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
new file mode 100644
index 0000000..485571a
--- /dev/null
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2019, 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 IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
+#define IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
+
+#include <cstdint>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android/hardware/identity/1.0/types.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace support {
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+// ---------------------------------------------------------------------------
+// Miscellaneous utilities.
+// ---------------------------------------------------------------------------
+
+// Dumps the data in |data| to stderr. The written data will be of the following
+// form for the call hexdump("signature", data) where |data| is of size 71:
+//
+// signature: dumping 71 bytes
+// 0000 30 45 02 21 00 ac c6 12 60 56 a2 e9 ee 16 be 14 0E.!....`V......
+// 0010 69 7f c4 00 95 8c e8 55 1f 22 de 34 0b 08 8a 3b i......U.".4...;
+// 0020 a0 56 54 05 07 02 20 58 77 d9 8c f9 eb 41 df fd .VT... Xw....A..
+// 0030 c1 a3 14 e0 bf b0 a2 c5 0c b6 85 8c 4a 0d f9 2b ............J..+
+// 0040 b7 8f d2 1d 9b 11 ac .......
+//
+// This should only be used for debugging.
+void hexdump(const string& name, const vector<uint8_t>& data);
+
+string encodeHex(const string& str);
+
+string encodeHex(const vector<uint8_t>& data);
+
+string encodeHex(const uint8_t* data, size_t dataLen);
+
+optional<vector<uint8_t>> decodeHex(const string& hexEncoded);
+
+// ---------------------------------------------------------------------------
+// CBOR utilities.
+// ---------------------------------------------------------------------------
+
+// Returns pretty-printed CBOR for |value|.
+//
+// Only valid CBOR should be passed to this function.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+// The |mapKeysToNotPrint| parameter specifies the name of map values
+// to not print. This is useful for unit tests.
+string cborPrettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize = 32,
+ const vector<string>& mapKeysToNotPrint = {});
+
+// ---------------------------------------------------------------------------
+// Crypto functionality / abstraction.
+// ---------------------------------------------------------------------------
+
+constexpr size_t kAesGcmIvSize = 12;
+constexpr size_t kAesGcmTagSize = 16;
+constexpr size_t kAes128GcmKeySize = 16;
+
+// Returns |numBytes| bytes of random data.
+optional<vector<uint8_t>> getRandom(size_t numBytes);
+
+// Calculates the SHA-256 of |data|.
+vector<uint8_t> sha256(const vector<uint8_t>& data);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext. The format of |encryptedData| must
+// be as specified in the encryptAes128Gcm() function.
+optional<vector<uint8_t>> decryptAes128Gcm(const vector<uint8_t>& key,
+ const vector<uint8_t>& encryptedData,
+ const vector<uint8_t>& additionalAuthenticatedData);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag).
+optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vector<uint8_t>& nonce,
+ const vector<uint8_t>& data,
+ const vector<uint8_t>& additionalAuthenticatedData);
+
+// ---------------------------------------------------------------------------
+// EC crypto functionality / abstraction (only supports P-256).
+// ---------------------------------------------------------------------------
+
+// Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the
+// PKCS#8 encoded key-pair.
+//
+optional<vector<uint8_t>> createEcKeyPair();
+
+// For an EC key |keyPair| encoded in PKCS#8 format, extracts the public key in
+// uncompressed point form.
+//
+optional<vector<uint8_t>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair);
+
+// For an EC key |keyPair| encoded in PKCS#8 format, extracts the private key as
+// an EC uncompressed key.
+//
+optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair);
+
+// For an EC key |keyPair| encoded in PKCS#8 format, creates a PKCS#12 structure
+// with the key-pair (not using a password to encrypt the data). The public key
+// in the created structure is included as a certificate, using the given fields
+// |serialDecimal|, |issuer|, |subject|, |validityNotBefore|, and
+// |validityNotAfter|.
+//
+optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, const string& name,
+ const string& serialDecimal, const string& issuer,
+ const string& subject, time_t validityNotBefore,
+ time_t validityNotAfter);
+
+// Signs |data| with |key| (which must be in the format returned by
+// ecKeyPairGetPrivateKey()). Signature is returned and will be in DER format.
+//
+optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data);
+
+// Calculates the HMAC with SHA-256 for |data| using |key|. The calculated HMAC
+// is returned and will be 32 bytes.
+//
+optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<uint8_t>& data);
+
+// Checks that |signature| (in DER format) is a valid signature of |digest|,
+// made with |publicKey| (which must be in the format returned by
+// ecKeyPairGetPublicKey()).
+//
+bool checkEcDsaSignature(const vector<uint8_t>& digest, const vector<uint8_t>& signature,
+ const vector<uint8_t>& publicKey);
+
+// Extracts the public-key from the top-most certificate in |certificateChain|
+// (which should be a concatenated chain of DER-encoded X.509 certificates).
+//
+// The returned public key will be in the same format as returned by
+// ecKeyPairGetPublicKey().
+//
+optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& certificateChain);
+
+// Generates a X.509 certificate for |publicKey| (which must be in the format
+// returned by ecKeyPairGetPublicKey()).
+//
+// The certificate is signed by |signingKey| (which must be in the format
+// returned by ecKeyPairGetPrivateKey())
+//
+optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
+ const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
+ const string& serialDecimal, const string& issuer, const string& subject,
+ time_t validityNotBefore, time_t validityNotAfter);
+
+// Performs Elliptic-curve Diffie-Helman using |publicKey| (which must be in the
+// format returned by ecKeyPairGetPublicKey()) and |privateKey| (which must be
+// in the format returned by ecKeyPairGetPrivateKey()).
+//
+// On success, the computed shared secret is returned.
+//
+optional<vector<uint8_t>> ecdh(const vector<uint8_t>& publicKey, const vector<uint8_t>& privateKey);
+
+// Key derivation function using SHA-256, conforming to RFC 5869.
+//
+// On success, the derived key is returned.
+//
+optional<vector<uint8_t>> hkdf(const vector<uint8_t>& sharedSecret, const vector<uint8_t>& salt,
+ const vector<uint8_t>& info, size_t size);
+
+// Returns the X and Y coordinates from |publicKey| (which must be in the format
+// returned by ecKeyPairGetPublicKey()).
+//
+// Success is indicated by the first value in the returned tuple. If successful,
+// the returned coordinates will be in uncompressed form.
+//
+tuple<bool, vector<uint8_t>, vector<uint8_t>> ecPublicKeyGetXandY(const vector<uint8_t>& publicKey);
+
+// Concatenates all certificates into |certificateChain| together into a
+// single bytestring.
+//
+// This is the reverse operation of certificateChainSplit().
+vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain);
+
+// Splits all the certificates in a single bytestring into individual
+// certificates.
+//
+// Returns nothing if |certificateChain| contains invalid data.
+//
+// This is the reverse operation of certificateChainJoin().
+optional<vector<vector<uint8_t>>> certificateChainSplit(const vector<uint8_t>& certificateChain);
+
+// Validates that the certificate chain is valid. In particular, checks that each
+// certificate in the chain is signed by the public key in the following certificate.
+//
+// Returns false if |certificateChain| failed validation or if each certificate
+// is not signed by its successor.
+//
+bool certificateChainValidate(const vector<uint8_t>& certificateChain);
+
+// Signs |data| and |detachedContent| with |key| (which must be in the format
+// returned by ecKeyPairGetPrivateKey()).
+//
+// On success, the Signature is returned and will be in COSE_Sign1 format.
+//
+// If |certificateChain| is non-empty it's included in the 'x5chain'
+// protected header element (as as described in'draft-ietf-cose-x509-04').
+//
+optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent,
+ const vector<uint8_t>& certificateChain);
+
+// Checks that |signatureCoseSign1| (in COSE_Sign1 format) is a valid signature
+// made with |public_key| (which must be in the format returned by
+// ecKeyPairGetPublicKey()) where |detachedContent| is the detached content.
+//
+bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
+ const vector<uint8_t>& detachedContent,
+ const vector<uint8_t>& publicKey);
+
+// Extracts the payload from a COSE_Sign1.
+optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1);
+
+// Extracts the X.509 certificate chain, if present. Returns the data as a
+// concatenated chain of DER-encoded X.509 certificates
+//
+// Returns nothing if there is no 'x5chain' element or an error occurs.
+//
+optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCoseSign1);
+
+// MACs |data| and |detachedContent| with |key| (which can be any sequence of
+// bytes).
+//
+// If successful, the MAC is returned and will be in COSE_Mac0 format.
+//
+optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent);
+
+// ---------------------------------------------------------------------------
+// Platform abstraction.
+// ---------------------------------------------------------------------------
+
+// Returns the hardware-bound AES-128 key.
+const vector<uint8_t>& getHardwareBoundKey();
+
+// ---------------------------------------------------------------------------
+// Utility functions specific to IdentityCredential.
+// ---------------------------------------------------------------------------
+
+// Returns a reference to a Result with code OK and empty message.
+const Result& resultOK();
+
+// Returns a new Result with the given code and message.
+Result result(ResultCode code, const char* format, ...) __attribute__((format(printf, 2, 3)));
+
+// Splits the given bytestring into chunks. If the given vector is smaller or equal to
+// |maxChunkSize| a vector with |content| as the only element is returned. Otherwise
+// |content| is split into N vectors each of size |maxChunkSize| except the final element
+// may be smaller than |maxChunkSize|.
+vector<vector<uint8_t>> chunkVector(const vector<uint8_t>& content, size_t maxChunkSize);
+
+// Calculates the MAC for |profile| using |storageKey|.
+optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
+ const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
+
+// Checks authenticity of the MAC in |profile| using |storageKey|.
+bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
+ const vector<uint8_t>& storageKey);
+
+// Returns the testing AES-128 key where all bits are set to 0.
+const vector<uint8_t>& getTestHardwareBoundKey();
+
+// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+ const vector<uint16_t> accessControlProfileIds);
+
+} // namespace support
+} // namespace identity
+} // namespace hardware
+} // namespace android
+
+#endif // IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
diff --git a/identity/support/include/cppbor/README.md b/identity/support/include/cppbor/README.md
new file mode 100644
index 0000000..723bfcf
--- /dev/null
+++ b/identity/support/include/cppbor/README.md
@@ -0,0 +1,216 @@
+CppBor: A Modern C++ CBOR Parser and Generator
+==============================================
+
+CppBor provides a natural and easy-to-use syntax for constructing and
+parsing CBOR messages. It does not (yet) support all features of
+CBOR, nor (yet) support validation against CDDL schemata, though both
+are planned. CBOR features that aren't supported include:
+
+* Indefinite length values
+* Semantic tagging
+* Floating point
+
+CppBor requires C++-17.
+
+## CBOR representation
+
+CppBor represents CBOR data items as instances of the `Item` class or,
+more precisely, as instances of subclasses of `Item`, since `Item` is a
+pure interface. The subclasses of `Item` correspond almost one-to-one
+with CBOR major types, and are named to match the CDDL names to which
+they correspond. They are:
+
+* `Uint` corresponds to major type 0, and can hold unsigned integers
+ up through (2^64 - 1).
+* `Nint` corresponds to major type 1. It can only hold values from -1
+ to -(2^63 - 1), since it's internal representation is an int64_t.
+ This can be fixed, but it seems unlikely that applications will need
+ the omitted range from -(2^63) to (2^64 - 1), since it's
+ inconvenient to represent them in many programming languages.
+* `Int` is an abstract base of `Uint` and `Nint` that facilitates
+ working with all signed integers representable with int64_t.
+* `Bstr` corresponds to major type 2, a byte string.
+* `Tstr` corresponds to major type 3, a text string.
+* `Array` corresponds to major type 4, an Array. It holds a
+ variable-length array of `Item`s.
+* `Map` corresponds to major type 5, a Map. It holds a
+ variable-length array of pairs of `Item`s.
+* `Simple` corresponds to major type 7. It's an abstract class since
+ items require more specific type.
+* `Bool` is the only currently-implemented subclass of `Simple`.
+
+Note that major type 6, semantic tag, is not yet implemented.
+
+In practice, users of CppBor will rarely use most of these classes
+when generating CBOR encodings. This is because CppBor provides
+straightforward conversions from the obvious normal C++ types.
+Specifically, the following conversions are provided in appropriate
+contexts:
+
+* Signed and unsigned integers convert to `Uint` or `Nint`, as
+ appropriate.
+* `std::string`, `std::string_view`, `const char*` and
+ `std::pair<char iterator, char iterator>` convert to `Tstr`.
+* `std::vector<uint8_t>`, `std::pair<uint8_t iterator, uint8_t
+ iterator>` and `std::pair<uint8_t*, size_t>` convert to `Bstr`.
+* `bool` converts to `Bool`.
+
+## CBOR generation
+
+### Complete tree generation
+
+The set of `encode` methods in `Item` provide the interface for
+producing encoded CBOR. The basic process for "complete tree"
+generation (as opposed to "incremental" generation, which is discussed
+below) is to construct an `Item` which models the data to be encoded,
+and then call one of the `encode` methods, whichever is convenient for
+the encoding destination. A trivial example:
+
+```
+cppbor::Uint val(0);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+ It's relatively rare that single values are encoded as above. More often, the
+ "root" data item will be an `Array` or `Map` which contains a more complex structure.For example
+ :
+
+``` using cppbor::Map;
+using cppbor::Array;
+
+std::vector<uint8_t> vec = // ...
+ Map val("key1", Array(Map("key_a", 99 "key_b", vec), "foo"), "key2", true);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+This creates a map with two entries, with `Tstr` keys "Outer1" and
+"Outer2", respectively. The "Outer1" entry has as its value an
+`Array` containing a `Map` and a `Tstr`. The "Outer2" entry has a
+`Bool` value.
+
+This example demonstrates how automatic conversion of C++ types to
+CppBor `Item` subclass instances is done. Where the caller provides a
+C++ or C string, a `Tstr` entry is added. Where the caller provides
+an integer literal or variable, a `Uint` or `Nint` is added, depending
+on whether the value is positive or negative.
+
+As an alternative, a more fluent-style API is provided for building up
+structures. For example:
+
+```
+using cppbor::Map;
+using cppbor::Array;
+
+std::vector<uint8_t> vec = // ...
+ Map val();
+val.add("key1", Array().add(Map().add("key_a", 99).add("key_b", vec)).add("foo")).add("key2", true);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+ An advantage of this interface over the constructor -
+ based creation approach above is that it need not be done all at once
+ .The `add` methods return a reference to the object added to to allow calls to be chained,
+ but chaining is not necessary; calls can be made
+sequentially, as the data to add is available.
+
+#### `encode` methods
+
+There are several variations of `Item::encode`, all of which
+accomplish the same task but output the encoded data in different
+ways, and with somewhat different performance characteristics. The
+provided options are:
+
+* `bool encode(uint8\_t** pos, const uint8\_t* end)` encodes into the
+ buffer referenced by the range [`*pos`, end). `*pos` is moved. If
+ the encoding runs out of buffer space before finishing, the method
+ returns false. This is the most efficient way to encode, into an
+ already-allocated buffer.
+* `void encode(EncodeCallback encodeCallback)` calls `encodeCallback`
+ for each encoded byte. It's the responsibility of the implementor
+ of the callback to behave safely in the event that the output buffer
+ (if applicable) is exhausted. This is less efficient than the prior
+ method because it imposes an additional function call for each byte.
+* `template </*...*/> void encode(OutputIterator i)`
+ encodes into the provided iterator. SFINAE ensures that the
+ template doesn't match for non-iterators. The implementation
+ actually uses the callback-based method, plus has whatever overhead
+ the iterator adds.
+* `std::vector<uint8_t> encode()` creates a new std::vector, reserves
+ sufficient capacity to hold the encoding, and inserts the encoded
+ bytes with a std::pushback_iterator and the previous method.
+* `std::string toString()` does the same as the previous method, but
+ returns a string instead of a vector.
+
+### Incremental generation
+
+Incremental generation requires deeper understanding of CBOR, because
+the library can't do as much to ensure that the output is valid. The
+basic tool for intcremental generation is the `encodeHeader`
+function. There are two variations, one which writes into a buffer,
+and one which uses a callback. Both simply write out the bytes of a
+header. To construct the same map as in the above examples,
+incrementally, one might write:
+
+```
+using namespace cppbor; // For example brevity
+
+std::vector encoding;
+auto iter = std::back_inserter(result);
+encodeHeader(MAP, 2 /* # of map entries */, iter);
+std::string s = "key1";
+encodeHeader(TSTR, s.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+encodeHeader(ARRAY, 2 /* # of array entries */, iter);
+Map().add("key_a", 99).add("key_b", vec).encode(iter)
+s = "foo";
+encodeHeader(TSTR, foo.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+s = "key2";
+encodeHeader(TSTR, foo.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+encodeHeader(SIMPLE, TRUE, iter);
+```
+
+As the above example demonstrates, the styles can be mixed -- Note the
+creation and encoding of the inner Map using the fluent style.
+
+## Parsing
+
+CppBor also supports parsing of encoded CBOR data, with the same
+feature set as encoding. There are two basic approaches to parsing,
+"full" and "stream"
+
+### Full parsing
+
+Full parsing means completely parsing a (possibly-compound) data
+item from a byte buffer. The `parse` functions that do not take a
+`ParseClient` pointer do this. They return a `ParseResult` which is a
+tuple of three values:
+
+* std::unique_ptr<Item> that points to the parsed item, or is nullptr
+ if there was a parse error.
+* const uint8_t* that points to the byte after the end of the decoded
+ item, or to the first unparseable byte in the event of an error.
+* std::string that is empty on success or contains an error message if
+ a parse error occurred.
+
+Assuming a successful parse, you can then use `Item::type()` to
+discover the type of the parsed item (e.g. MAP), and then use the
+appropriate `Item::as*()` method (e.g. `Item::asMap()`) to get a
+pointer to an interface which allows you to retrieve specific values.
+
+### Stream parsing
+
+Stream parsing is more complex, but more flexible. To use
+StreamParsing, you must create your own subclass of `ParseClient` and
+call one of the `parse` functions that accepts it. See the
+`ParseClient` methods docstrings for details.
+
+One unusual feature of stream parsing is that the `ParseClient`
+callback methods not only provide the parsed Item, but also pointers
+to the portion of the buffer that encode that Item. This is useful
+if, for example, you want to find an element inside of a structure,
+and then copy the encoding of that sub-structure, without bothering to
+parse the rest.
+
+The full parser is implemented with the stream parser.
diff --git a/identity/support/include/cppbor/cppbor.h b/identity/support/include/cppbor/cppbor.h
new file mode 100644
index 0000000..a755db1
--- /dev/null
+++ b/identity/support/include/cppbor/cppbor.h
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2019, 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+namespace cppbor {
+
+enum MajorType : uint8_t {
+ UINT = 0 << 5,
+ NINT = 1 << 5,
+ BSTR = 2 << 5,
+ TSTR = 3 << 5,
+ ARRAY = 4 << 5,
+ MAP = 5 << 5,
+ SEMANTIC = 6 << 5,
+ SIMPLE = 7 << 5,
+};
+
+enum SimpleType {
+ BOOLEAN,
+ NULL_T, // Only two supported, as yet.
+};
+
+enum SpecialAddlInfoValues : uint8_t {
+ FALSE = 20,
+ TRUE = 21,
+ NULL_V = 22,
+ ONE_BYTE_LENGTH = 24,
+ TWO_BYTE_LENGTH = 25,
+ FOUR_BYTE_LENGTH = 26,
+ EIGHT_BYTE_LENGTH = 27,
+};
+
+class Item;
+class Uint;
+class Nint;
+class Int;
+class Tstr;
+class Bstr;
+class Simple;
+class Bool;
+class Array;
+class Map;
+class Null;
+class Semantic;
+
+/**
+ * Returns the size of a CBOR header that contains the additional info value addlInfo.
+ */
+size_t headerSize(uint64_t addlInfo);
+
+/**
+ * Encodes a CBOR header with the specified type and additional info into the range [pos, end).
+ * Returns a pointer to one past the last byte written, or nullptr if there isn't sufficient space
+ * to write the header.
+ */
+uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end);
+
+using EncodeCallback = std::function<void(uint8_t)>;
+
+/**
+ * Encodes a CBOR header with the specified type and additional info, passing each byte in turn to
+ * encodeCallback.
+ */
+void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback);
+
+/**
+ * Encodes a CBOR header with the specified type and additional info, writing each byte to the
+ * provided OutputIterator.
+ */
+template <typename OutputIterator,
+ typename = std::enable_if_t<std::is_base_of_v<
+ std::output_iterator_tag,
+ typename std::iterator_traits<OutputIterator>::iterator_category>>>
+void encodeHeader(MajorType type, uint64_t addlInfo, OutputIterator iter) {
+ return encodeHeader(type, addlInfo, [&](uint8_t v) { *iter++ = v; });
+}
+
+/**
+ * Item represents a CBOR-encodeable data item. Item is an abstract interface with a set of virtual
+ * methods that allow encoding of the item or conversion to the appropriate derived type.
+ */
+class Item {
+ public:
+ virtual ~Item() {}
+
+ /**
+ * Returns the CBOR type of the item.
+ */
+ virtual MajorType type() const = 0;
+
+ // These methods safely downcast an Item to the appropriate subclass.
+ virtual const Int* asInt() const { return nullptr; }
+ virtual const Uint* asUint() const { return nullptr; }
+ virtual const Nint* asNint() const { return nullptr; }
+ virtual const Tstr* asTstr() const { return nullptr; }
+ virtual const Bstr* asBstr() const { return nullptr; }
+ virtual const Simple* asSimple() const { return nullptr; }
+ virtual const Map* asMap() const { return nullptr; }
+ virtual const Array* asArray() const { return nullptr; }
+ virtual const Semantic* asSemantic() const { return nullptr; }
+
+ /**
+ * Returns true if this is a "compound" item, i.e. one that contains one or more other items.
+ */
+ virtual bool isCompound() const { return false; }
+
+ bool operator==(const Item& other) const&;
+ bool operator!=(const Item& other) const& { return !(*this == other); }
+
+ /**
+ * Returns the number of bytes required to encode this Item into CBOR. Note that if this is a
+ * complex Item, calling this method will require walking the whole tree.
+ */
+ virtual size_t encodedSize() const = 0;
+
+ /**
+ * Encodes the Item into buffer referenced by range [*pos, end). Returns a pointer to one past
+ * the last position written. Returns nullptr if there isn't enough space to encode.
+ */
+ virtual uint8_t* encode(uint8_t* pos, const uint8_t* end) const = 0;
+
+ /**
+ * Encodes the Item by passing each encoded byte to encodeCallback.
+ */
+ virtual void encode(EncodeCallback encodeCallback) const = 0;
+
+ /**
+ * Clones the Item
+ */
+ virtual std::unique_ptr<Item> clone() const = 0;
+
+ /**
+ * Encodes the Item into the provided OutputIterator.
+ */
+ template <typename OutputIterator,
+ typename = typename std::iterator_traits<OutputIterator>::iterator_category>
+ void encode(OutputIterator i) const {
+ return encode([&](uint8_t v) { *i++ = v; });
+ }
+
+ /**
+ * Encodes the Item into a new std::vector<uint8_t>.
+ */
+ std::vector<uint8_t> encode() const {
+ std::vector<uint8_t> retval;
+ retval.reserve(encodedSize());
+ encode(std::back_inserter(retval));
+ return retval;
+ }
+
+ /**
+ * Encodes the Item into a new std::string.
+ */
+ std::string toString() const {
+ std::string retval;
+ retval.reserve(encodedSize());
+ encode([&](uint8_t v) { retval.push_back(v); });
+ return retval;
+ }
+
+ /**
+ * Encodes only the header of the Item.
+ */
+ inline uint8_t* encodeHeader(uint64_t addlInfo, uint8_t* pos, const uint8_t* end) const {
+ return ::cppbor::encodeHeader(type(), addlInfo, pos, end);
+ }
+
+ /**
+ * Encodes only the header of the Item.
+ */
+ inline void encodeHeader(uint64_t addlInfo, EncodeCallback encodeCallback) const {
+ ::cppbor::encodeHeader(type(), addlInfo, encodeCallback);
+ }
+};
+
+/**
+ * Int is an abstraction that allows Uint and Nint objects to be manipulated without caring about
+ * the sign.
+ */
+class Int : public Item {
+ public:
+ bool operator==(const Int& other) const& { return value() == other.value(); }
+
+ virtual int64_t value() const = 0;
+
+ const Int* asInt() const override { return this; }
+};
+
+/**
+ * Uint is a concrete Item that implements CBOR major type 0.
+ */
+class Uint : public Int {
+ public:
+ static constexpr MajorType kMajorType = UINT;
+
+ explicit Uint(uint64_t v) : mValue(v) {}
+
+ bool operator==(const Uint& other) const& { return mValue == other.mValue; }
+
+ MajorType type() const override { return kMajorType; }
+ const Uint* asUint() const override { return this; }
+
+ size_t encodedSize() const override { return headerSize(mValue); }
+
+ int64_t value() const override { return mValue; }
+ uint64_t unsignedValue() const { return mValue; }
+
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+ return encodeHeader(mValue, pos, end);
+ }
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(mValue, encodeCallback);
+ }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Uint>(mValue); }
+
+ private:
+ uint64_t mValue;
+};
+
+/**
+ * Nint is a concrete Item that implements CBOR major type 1.
+
+ * Note that it is incapable of expressing the full range of major type 1 values, becaue it can only
+ * express values that fall into the range [std::numeric_limits<int64_t>::min(), -1]. It cannot
+ * express values in the range [std::numeric_limits<int64_t>::min() - 1,
+ * -std::numeric_limits<uint64_t>::max()].
+ */
+class Nint : public Int {
+ public:
+ static constexpr MajorType kMajorType = NINT;
+
+ explicit Nint(int64_t v);
+
+ bool operator==(const Nint& other) const& { return mValue == other.mValue; }
+
+ MajorType type() const override { return kMajorType; }
+ const Nint* asNint() const override { return this; }
+ size_t encodedSize() const override { return headerSize(addlInfo()); }
+
+ int64_t value() const override { return mValue; }
+
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+ return encodeHeader(addlInfo(), pos, end);
+ }
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(addlInfo(), encodeCallback);
+ }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Nint>(mValue); }
+
+ private:
+ uint64_t addlInfo() const { return -1ll - mValue; }
+
+ int64_t mValue;
+};
+
+/**
+ * Bstr is a concrete Item that implements major type 2.
+ */
+class Bstr : public Item {
+ public:
+ static constexpr MajorType kMajorType = BSTR;
+
+ // Construct from a vector
+ explicit Bstr(std::vector<uint8_t> v) : mValue(std::move(v)) {}
+
+ // Construct from a string
+ explicit Bstr(const std::string& v)
+ : mValue(reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()) + v.size()) {}
+
+ // Construct from a pointer/size pair
+ explicit Bstr(const std::pair<const uint8_t*, size_t>& buf)
+ : mValue(buf.first, buf.first + buf.second) {}
+
+ // Construct from a pair of iterators
+ template <typename I1, typename I2,
+ typename = typename std::iterator_traits<I1>::iterator_category,
+ typename = typename std::iterator_traits<I2>::iterator_category>
+ explicit Bstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {}
+
+ // Construct from an iterator range.
+ template <typename I1, typename I2,
+ typename = typename std::iterator_traits<I1>::iterator_category,
+ typename = typename std::iterator_traits<I2>::iterator_category>
+ Bstr(I1 begin, I2 end) : mValue(begin, end) {}
+
+ bool operator==(const Bstr& other) const& { return mValue == other.mValue; }
+
+ MajorType type() const override { return kMajorType; }
+ const Bstr* asBstr() const override { return this; }
+ size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); }
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(mValue.size(), encodeCallback);
+ encodeValue(encodeCallback);
+ }
+
+ const std::vector<uint8_t>& value() const { return mValue; }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bstr>(mValue); }
+
+ private:
+ void encodeValue(EncodeCallback encodeCallback) const;
+
+ std::vector<uint8_t> mValue;
+};
+
+/**
+ * Bstr is a concrete Item that implements major type 3.
+ */
+class Tstr : public Item {
+ public:
+ static constexpr MajorType kMajorType = TSTR;
+
+ // Construct from a string
+ explicit Tstr(std::string v) : mValue(std::move(v)) {}
+
+ // Construct from a string_view
+ explicit Tstr(const std::string_view& v) : mValue(v) {}
+
+ // Construct from a C string
+ explicit Tstr(const char* v) : mValue(std::string(v)) {}
+
+ // Construct from a pair of iterators
+ template <typename I1, typename I2,
+ typename = typename std::iterator_traits<I1>::iterator_category,
+ typename = typename std::iterator_traits<I2>::iterator_category>
+ explicit Tstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {}
+
+ // Construct from an iterator range
+ template <typename I1, typename I2,
+ typename = typename std::iterator_traits<I1>::iterator_category,
+ typename = typename std::iterator_traits<I2>::iterator_category>
+ Tstr(I1 begin, I2 end) : mValue(begin, end) {}
+
+ bool operator==(const Tstr& other) const& { return mValue == other.mValue; }
+
+ MajorType type() const override { return kMajorType; }
+ const Tstr* asTstr() const override { return this; }
+ size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); }
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(mValue.size(), encodeCallback);
+ encodeValue(encodeCallback);
+ }
+
+ const std::string& value() const { return mValue; }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Tstr>(mValue); }
+
+ private:
+ void encodeValue(EncodeCallback encodeCallback) const;
+
+ std::string mValue;
+};
+
+/**
+ * CompoundItem is an abstract Item that provides common functionality for Items that contain other
+ * items, i.e. Arrays (CBOR type 4) and Maps (CBOR type 5).
+ */
+class CompoundItem : public Item {
+ public:
+ bool operator==(const CompoundItem& other) const&;
+
+ virtual size_t size() const { return mEntries.size(); }
+
+ bool isCompound() const override { return true; }
+
+ size_t encodedSize() const override {
+ return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()),
+ [](size_t sum, auto& entry) { return sum + entry->encodedSize(); });
+ }
+
+ using Item::encode; // Make base versions visible.
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+ void encode(EncodeCallback encodeCallback) const override;
+
+ virtual uint64_t addlInfo() const = 0;
+
+ protected:
+ std::vector<std::unique_ptr<Item>> mEntries;
+};
+
+/*
+ * Array is a concrete Item that implements CBOR major type 4.
+ *
+ * Note that Arrays are not copyable. This is because copying them is expensive and making them
+ * move-only ensures that they're never copied accidentally. If you actually want to copy an Array,
+ * use the clone() method.
+ */
+class Array : public CompoundItem {
+ public:
+ static constexpr MajorType kMajorType = ARRAY;
+
+ Array() = default;
+ Array(const Array& other) = delete;
+ Array(Array&&) = default;
+ Array& operator=(const Array&) = delete;
+ Array& operator=(Array&&) = default;
+
+ /**
+ * Construct an Array from a variable number of arguments of different types. See
+ * details::makeItem below for details on what types may be provided. In general, this accepts
+ * all of the types you'd expect and doest the things you'd expect (integral values are addes as
+ * Uint or Nint, std::string and char* are added as Tstr, bools are added as Bool, etc.).
+ */
+ template <typename... Args, typename Enable>
+ Array(Args&&... args);
+
+ /**
+ * Append a single element to the Array, of any compatible type.
+ */
+ template <typename T>
+ Array& add(T&& v) &;
+ template <typename T>
+ Array&& add(T&& v) &&;
+
+ const std::unique_ptr<Item>& operator[](size_t index) const { return mEntries[index]; }
+ std::unique_ptr<Item>& operator[](size_t index) { return mEntries[index]; }
+
+ MajorType type() const override { return kMajorType; }
+ const Array* asArray() const override { return this; }
+
+ virtual std::unique_ptr<Item> clone() const override;
+
+ uint64_t addlInfo() const override { return size(); }
+};
+
+/*
+ * Map is a concrete Item that implements CBOR major type 5.
+ *
+ * Note that Maps are not copyable. This is because copying them is expensive and making them
+ * move-only ensures that they're never copied accidentally. If you actually want to copy a
+ * Map, use the clone() method.
+ */
+class Map : public CompoundItem {
+ public:
+ static constexpr MajorType kMajorType = MAP;
+
+ Map() = default;
+ Map(const Map& other) = delete;
+ Map(Map&&) = default;
+ Map& operator=(const Map& other) = delete;
+ Map& operator=(Map&&) = default;
+
+ /**
+ * Construct a Map from a variable number of arguments of different types. An even number of
+ * arguments must be provided (this is verified statically). See details::makeItem below for
+ * details on what types may be provided. In general, this accepts all of the types you'd
+ * expect and doest the things you'd expect (integral values are addes as Uint or Nint,
+ * std::string and char* are added as Tstr, bools are added as Bool, etc.).
+ */
+ template <typename... Args, typename Enable>
+ Map(Args&&... args);
+
+ /**
+ * Append a key/value pair to the Map, of any compatible types.
+ */
+ template <typename Key, typename Value>
+ Map& add(Key&& key, Value&& value) &;
+ template <typename Key, typename Value>
+ Map&& add(Key&& key, Value&& value) &&;
+
+ size_t size() const override {
+ assertInvariant();
+ return mEntries.size() / 2;
+ }
+
+ template <typename Key, typename Enable>
+ std::pair<std::unique_ptr<Item>&, bool> get(Key key);
+
+ std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> operator[](
+ size_t index) const {
+ assertInvariant();
+ return {mEntries[index * 2], mEntries[index * 2 + 1]};
+ }
+
+ std::pair<std::unique_ptr<Item>&, std::unique_ptr<Item>&> operator[](size_t index) {
+ assertInvariant();
+ return {mEntries[index * 2], mEntries[index * 2 + 1]};
+ }
+
+ MajorType type() const override { return kMajorType; }
+ const Map* asMap() const override { return this; }
+
+ virtual std::unique_ptr<Item> clone() const override;
+
+ uint64_t addlInfo() const override { return size(); }
+
+ private:
+ void assertInvariant() const;
+};
+
+class Semantic : public CompoundItem {
+ public:
+ static constexpr MajorType kMajorType = SEMANTIC;
+
+ template <typename T>
+ explicit Semantic(uint64_t value, T&& child);
+
+ Semantic(const Semantic& other) = delete;
+ Semantic(Semantic&&) = default;
+ Semantic& operator=(const Semantic& other) = delete;
+ Semantic& operator=(Semantic&&) = default;
+
+ size_t size() const override {
+ assertInvariant();
+ return 1;
+ }
+
+ size_t encodedSize() const override {
+ return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(mValue),
+ [](size_t sum, auto& entry) { return sum + entry->encodedSize(); });
+ }
+
+ MajorType type() const override { return kMajorType; }
+ const Semantic* asSemantic() const override { return this; }
+
+ const std::unique_ptr<Item>& child() const {
+ assertInvariant();
+ return mEntries[0];
+ }
+
+ std::unique_ptr<Item>& child() {
+ assertInvariant();
+ return mEntries[0];
+ }
+
+ uint64_t value() const { return mValue; }
+
+ uint64_t addlInfo() const override { return value(); }
+
+ virtual std::unique_ptr<Item> clone() const override {
+ assertInvariant();
+ return std::make_unique<Semantic>(mValue, mEntries[0]->clone());
+ }
+
+ protected:
+ Semantic() = default;
+ Semantic(uint64_t value) : mValue(value) {}
+ uint64_t mValue;
+
+ private:
+ void assertInvariant() const;
+};
+
+/**
+ * Simple is abstract Item that implements CBOR major type 7. It is intended to be subclassed to
+ * create concrete Simple types. At present only Bool is provided.
+ */
+class Simple : public Item {
+ public:
+ static constexpr MajorType kMajorType = SIMPLE;
+
+ bool operator==(const Simple& other) const&;
+
+ virtual SimpleType simpleType() const = 0;
+ MajorType type() const override { return kMajorType; }
+
+ const Simple* asSimple() const override { return this; }
+
+ virtual const Bool* asBool() const { return nullptr; };
+ virtual const Null* asNull() const { return nullptr; };
+};
+
+/**
+ * Bool is a concrete type that implements CBOR major type 7, with additional item values for TRUE
+ * and FALSE.
+ */
+class Bool : public Simple {
+ public:
+ static constexpr SimpleType kSimpleType = BOOLEAN;
+
+ explicit Bool(bool v) : mValue(v) {}
+
+ bool operator==(const Bool& other) const& { return mValue == other.mValue; }
+
+ SimpleType simpleType() const override { return kSimpleType; }
+ const Bool* asBool() const override { return this; }
+
+ size_t encodedSize() const override { return 1; }
+
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+ return encodeHeader(mValue ? TRUE : FALSE, pos, end);
+ }
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(mValue ? TRUE : FALSE, encodeCallback);
+ }
+
+ bool value() const { return mValue; }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bool>(mValue); }
+
+ private:
+ bool mValue;
+};
+
+/**
+ * Null is a concrete type that implements CBOR major type 7, with additional item value for NULL
+ */
+class Null : public Simple {
+ public:
+ static constexpr SimpleType kSimpleType = NULL_T;
+
+ explicit Null() {}
+
+ SimpleType simpleType() const override { return kSimpleType; }
+ const Null* asNull() const override { return this; }
+
+ size_t encodedSize() const override { return 1; }
+
+ using Item::encode;
+ uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+ return encodeHeader(NULL_V, pos, end);
+ }
+ void encode(EncodeCallback encodeCallback) const override {
+ encodeHeader(NULL_V, encodeCallback);
+ }
+
+ virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Null>(); }
+};
+
+template <typename T>
+std::unique_ptr<T> downcastItem(std::unique_ptr<Item>&& v) {
+ static_assert(std::is_base_of_v<Item, T> && !std::is_abstract_v<T>,
+ "returned type is not an Item or is an abstract class");
+ if (v && T::kMajorType == v->type()) {
+ if constexpr (std::is_base_of_v<Simple, T>) {
+ if (T::kSimpleType != v->asSimple()->simpleType()) {
+ return nullptr;
+ }
+ }
+ return std::unique_ptr<T>(static_cast<T*>(v.release()));
+ } else {
+ return nullptr;
+ }
+}
+
+/**
+ * Details. Mostly you shouldn't have to look below, except perhaps at the docstring for makeItem.
+ */
+namespace details {
+
+template <typename T, typename V, typename Enable = void>
+struct is_iterator_pair_over : public std::false_type {};
+
+template <typename I1, typename I2, typename V>
+struct is_iterator_pair_over<
+ std::pair<I1, I2>, V,
+ typename std::enable_if_t<std::is_same_v<V, typename std::iterator_traits<I1>::value_type>>>
+ : public std::true_type {};
+
+template <typename T, typename V, typename Enable = void>
+struct is_unique_ptr_of_subclass_of_v : public std::false_type {};
+
+template <typename T, typename P>
+struct is_unique_ptr_of_subclass_of_v<T, std::unique_ptr<P>,
+ typename std::enable_if_t<std::is_base_of_v<T, P>>>
+ : public std::true_type {};
+
+/* check if type is one of std::string (1), std::string_view (2), null-terminated char* (3) or pair
+ * of iterators (4)*/
+template <typename T, typename Enable = void>
+struct is_text_type_v : public std::false_type {};
+
+template <typename T>
+struct is_text_type_v<
+ T, typename std::enable_if_t<
+ /* case 1 */ //
+ std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string>
+ /* case 2 */ //
+ || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string_view>
+ /* case 3 */ //
+ || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, char*> //
+ || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, const char*>
+ /* case 4 */
+ || details::is_iterator_pair_over<T, char>::value>> : public std::true_type {};
+
+/**
+ * Construct a unique_ptr<Item> from many argument types. Accepts:
+ *
+ * (a) booleans;
+ * (b) integers, all sizes and signs;
+ * (c) text strings, as defined by is_text_type_v above;
+ * (d) byte strings, as std::vector<uint8_t>(d1), pair of iterators (d2) or pair<uint8_t*, size_T>
+ * (d3); and
+ * (e) Item subclass instances, including Array and Map. Items may be provided by naked pointer
+ * (e1), unique_ptr (e2), reference (e3) or value (e3). If provided by reference or value, will
+ * be moved if possible. If provided by pointer, ownership is taken.
+ * (f) null pointer;
+ */
+template <typename T>
+std::unique_ptr<Item> makeItem(T v) {
+ Item* p = nullptr;
+ if constexpr (/* case a */ std::is_same_v<T, bool>) {
+ p = new Bool(v);
+ } else if constexpr (/* case b */ std::is_integral_v<T>) { // b
+ if (v < 0) {
+ p = new Nint(v);
+ } else {
+ p = new Uint(static_cast<uint64_t>(v));
+ }
+ } else if constexpr (/* case c */ //
+ details::is_text_type_v<T>::value) {
+ p = new Tstr(v);
+ } else if constexpr (/* case d1 */ //
+ std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
+ std::vector<uint8_t>>
+ /* case d2 */ //
+ || details::is_iterator_pair_over<T, uint8_t>::value
+ /* case d3 */ //
+ || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
+ std::pair<uint8_t*, size_t>>) {
+ p = new Bstr(v);
+ } else if constexpr (/* case e1 */ //
+ std::is_pointer_v<T> &&
+ std::is_base_of_v<Item, std::remove_pointer_t<T>>) {
+ p = v;
+ } else if constexpr (/* case e2 */ //
+ details::is_unique_ptr_of_subclass_of_v<Item, T>::value) {
+ p = v.release();
+ } else if constexpr (/* case e3 */ //
+ std::is_base_of_v<Item, T>) {
+ p = new T(std::move(v));
+ } else if constexpr (/* case f */ std::is_null_pointer_v<T>) {
+ p = new Null();
+ } else {
+ // It's odd that this can't be static_assert(false), since it shouldn't be evaluated if one
+ // of the above ifs matches. But static_assert(false) always triggers.
+ static_assert(std::is_same_v<T, bool>, "makeItem called with unsupported type");
+ }
+ return std::unique_ptr<Item>(p);
+}
+
+} // namespace details
+
+template <typename... Args,
+ /* Prevent use as copy ctor */ typename = std::enable_if_t<
+ (sizeof...(Args)) != 1 ||
+ !(std::is_same_v<Array, std::remove_cv_t<std::remove_reference_t<Args>>> || ...)>>
+Array::Array(Args&&... args) {
+ mEntries.reserve(sizeof...(args));
+ (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...);
+}
+
+template <typename T>
+Array& Array::add(T&& v) & {
+ mEntries.push_back(details::makeItem(std::forward<T>(v)));
+ return *this;
+}
+
+template <typename T>
+Array&& Array::add(T&& v) && {
+ mEntries.push_back(details::makeItem(std::forward<T>(v)));
+ return std::move(*this);
+}
+
+template <typename... Args,
+ /* Prevent use as copy ctor */ typename = std::enable_if_t<(sizeof...(Args)) != 1>>
+Map::Map(Args&&... args) {
+ static_assert((sizeof...(Args)) % 2 == 0, "Map must have an even number of entries");
+ mEntries.reserve(sizeof...(args));
+ (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...);
+}
+
+template <typename Key, typename Value>
+Map& Map::add(Key&& key, Value&& value) & {
+ mEntries.push_back(details::makeItem(std::forward<Key>(key)));
+ mEntries.push_back(details::makeItem(std::forward<Value>(value)));
+ return *this;
+}
+
+template <typename Key, typename Value>
+Map&& Map::add(Key&& key, Value&& value) && {
+ this->add(std::forward<Key>(key), std::forward<Value>(value));
+ return std::move(*this);
+}
+
+template <typename Key, typename = std::enable_if_t<std::is_integral_v<Key> ||
+ details::is_text_type_v<Key>::value>>
+std::pair<std::unique_ptr<Item>&, bool> Map::get(Key key) {
+ assertInvariant();
+ auto keyItem = details::makeItem(key);
+ for (size_t i = 0; i < mEntries.size(); i += 2) {
+ if (*keyItem == *mEntries[i]) {
+ return {mEntries[i + 1], true};
+ }
+ }
+ return {keyItem, false};
+}
+
+template <typename T>
+Semantic::Semantic(uint64_t value, T&& child) : mValue(value) {
+ mEntries.reserve(1);
+ mEntries.push_back(details::makeItem(std::forward<T>(child)));
+}
+
+} // namespace cppbor
diff --git a/identity/support/include/cppbor/cppbor_parse.h b/identity/support/include/cppbor/cppbor_parse.h
new file mode 100644
index 0000000..66cd5a3
--- /dev/null
+++ b/identity/support/include/cppbor/cppbor_parse.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2019, 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.
+ */
+
+#pragma once
+
+#include "cppbor.h"
+
+namespace cppbor {
+
+using ParseResult = std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+ std::string /* errMsg */>;
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the range [begin, end).
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty. If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+ParseResult parse(const uint8_t* begin, const uint8_t* end);
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the byte vector.
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty. If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+inline ParseResult parse(const std::vector<uint8_t>& encoding) {
+ return parse(encoding.data(), encoding.data() + encoding.size());
+}
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size).
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty. If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+inline ParseResult parse(const uint8_t* begin, size_t size) {
+ return parse(begin, begin + size);
+}
+
+class ParseClient;
+
+/**
+ * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the
+ * provided ParseClient when elements are found.
+ */
+void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient);
+
+/**
+ * Parse the CBOR data in the vector in streaming fashion, calling methods on the
+ * provided ParseClient when elements are found.
+ */
+inline void parse(const std::vector<uint8_t>& encoding, ParseClient* parseClient) {
+ return parse(encoding.data(), encoding.data() + encoding.size(), parseClient);
+}
+
+/**
+ * A pure interface that callers of the streaming parse functions must implement.
+ */
+class ParseClient {
+ public:
+ virtual ~ParseClient() {}
+
+ /**
+ * Called when an item is found. The Item pointer points to the found item; use type() and
+ * the appropriate as*() method to examine the value. hdrBegin points to the first byte of the
+ * header, valueBegin points to the first byte of the value and end points one past the end of
+ * the item. In the case of header-only items, such as integers, and compound items (ARRAY,
+ * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to
+ * the byte past the header.
+ *
+ * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content. For
+ * Map and Array items, the size() method will return a correct value, but the index operators
+ * are unsafe, and the object cannot be safely compared with another Array/Map.
+ *
+ * The method returns a ParseClient*. In most cases "return this;" will be the right answer,
+ * but a different ParseClient may be returned, which the parser will begin using. If the method
+ * returns nullptr, parsing will be aborted immediately.
+ */
+ virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end) = 0;
+
+ /**
+ * Called when the end of a compound item (MAP or ARRAY) is found. The item argument will be
+ * the same one passed to the item() call -- and may be empty if item() moved its value out.
+ * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the
+ * first contained value, and one past the end of the last contained value, respectively.
+ *
+ * Note that the Item will have no content.
+ *
+ * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the
+ * parsing by returning nullptr;
+ */
+ virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end) = 0;
+
+ /**
+ * Called when parsing encounters an error. position is set to the first unparsed byte (one
+ * past the last successfully-parsed byte) and errorMessage contains an message explaining what
+ * sort of error occurred.
+ */
+ virtual void error(const uint8_t* position, const std::string& errorMessage) = 0;
+};
+
+} // namespace cppbor
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
new file mode 100644
index 0000000..7d93a4b
--- /dev/null
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -0,0 +1,1815 @@
+/*
+ * Copyright 2019, 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 "IdentityCredentialSupport"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#define _POSIX_C_SOURCE 199309L
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+#include <iomanip>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace support {
+
+using ::std::pair;
+using ::std::unique_ptr;
+
+// ---------------------------------------------------------------------------
+// Miscellaneous utilities.
+// ---------------------------------------------------------------------------
+
+void hexdump(const string& name, const vector<uint8_t>& data) {
+ fprintf(stderr, "%s: dumping %zd bytes\n", name.c_str(), data.size());
+ size_t n, m, o;
+ for (n = 0; n < data.size(); n += 16) {
+ fprintf(stderr, "%04zx ", n);
+ for (m = 0; m < 16 && n + m < data.size(); m++) {
+ fprintf(stderr, "%02x ", data[n + m]);
+ }
+ for (o = m; o < 16; o++) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " ");
+ for (m = 0; m < 16 && n + m < data.size(); m++) {
+ int c = data[n + m];
+ fprintf(stderr, "%c", isprint(c) ? c : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\n");
+}
+
+string encodeHex(const uint8_t* data, size_t dataLen) {
+ static const char hexDigits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ string ret;
+ ret.resize(dataLen * 2);
+ for (size_t n = 0; n < dataLen; n++) {
+ uint8_t byte = data[n];
+ ret[n * 2 + 0] = hexDigits[byte >> 4];
+ ret[n * 2 + 1] = hexDigits[byte & 0x0f];
+ }
+
+ return ret;
+}
+
+string encodeHex(const string& str) {
+ return encodeHex(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+string encodeHex(const vector<uint8_t>& data) {
+ return encodeHex(data.data(), data.size());
+}
+
+// Returns -1 on error, otherwise an integer in the range 0 through 15, both inclusive.
+int parseHexDigit(char hexDigit) {
+ if (hexDigit >= '0' && hexDigit <= '9') {
+ return int(hexDigit) - '0';
+ } else if (hexDigit >= 'a' && hexDigit <= 'f') {
+ return int(hexDigit) - 'a' + 10;
+ } else if (hexDigit >= 'A' && hexDigit <= 'F') {
+ return int(hexDigit) - 'A' + 10;
+ }
+ return -1;
+}
+
+optional<vector<uint8_t>> decodeHex(const string& hexEncoded) {
+ vector<uint8_t> out;
+ size_t hexSize = hexEncoded.size();
+ if ((hexSize & 1) != 0) {
+ LOG(ERROR) << "Size of data cannot be odd";
+ return {};
+ }
+
+ out.resize(hexSize / 2);
+ for (size_t n = 0; n < hexSize / 2; n++) {
+ int upperNibble = parseHexDigit(hexEncoded[n * 2]);
+ int lowerNibble = parseHexDigit(hexEncoded[n * 2 + 1]);
+ if (upperNibble == -1 || lowerNibble == -1) {
+ LOG(ERROR) << "Invalid hex digit at position " << n;
+ return {};
+ }
+ out[n] = (upperNibble << 4) + lowerNibble;
+ }
+
+ return out;
+}
+
+// ---------------------------------------------------------------------------
+// CBOR utilities.
+// ---------------------------------------------------------------------------
+
+static bool cborAreAllElementsNonCompound(const cppbor::CompoundItem* compoundItem) {
+ if (compoundItem->type() == cppbor::ARRAY) {
+ const cppbor::Array* array = compoundItem->asArray();
+ for (size_t n = 0; n < array->size(); n++) {
+ const cppbor::Item* entry = (*array)[n].get();
+ switch (entry->type()) {
+ case cppbor::ARRAY:
+ case cppbor::MAP:
+ return false;
+ default:
+ break;
+ }
+ }
+ } else {
+ const cppbor::Map* map = compoundItem->asMap();
+ for (size_t n = 0; n < map->size(); n++) {
+ auto [keyEntry, valueEntry] = (*map)[n];
+ switch (keyEntry->type()) {
+ case cppbor::ARRAY:
+ case cppbor::MAP:
+ return false;
+ default:
+ break;
+ }
+ switch (valueEntry->type()) {
+ case cppbor::ARRAY:
+ case cppbor::MAP:
+ return false;
+ default:
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static bool cborPrettyPrintInternal(const cppbor::Item* item, string& out, size_t indent,
+ size_t maxBStrSize, const vector<string>& mapKeysToNotPrint) {
+ char buf[80];
+
+ string indentString(indent, ' ');
+
+ switch (item->type()) {
+ case cppbor::UINT:
+ snprintf(buf, sizeof(buf), "%" PRIu64, item->asUint()->unsignedValue());
+ out.append(buf);
+ break;
+
+ case cppbor::NINT:
+ snprintf(buf, sizeof(buf), "%" PRId64, item->asNint()->value());
+ out.append(buf);
+ break;
+
+ case cppbor::BSTR: {
+ const cppbor::Bstr* bstr = item->asBstr();
+ const vector<uint8_t>& value = bstr->value();
+ if (value.size() > maxBStrSize) {
+ unsigned char digest[SHA_DIGEST_LENGTH];
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, value.data(), value.size());
+ SHA1_Final(digest, &ctx);
+ char buf2[SHA_DIGEST_LENGTH * 2 + 1];
+ for (size_t n = 0; n < SHA_DIGEST_LENGTH; n++) {
+ snprintf(buf2 + n * 2, 3, "%02x", digest[n]);
+ }
+ snprintf(buf, sizeof(buf), "<bstr size=%zd sha1=%s>", value.size(), buf2);
+ out.append(buf);
+ } else {
+ out.append("{");
+ for (size_t n = 0; n < value.size(); n++) {
+ if (n > 0) {
+ out.append(", ");
+ }
+ snprintf(buf, sizeof(buf), "0x%02x", value[n]);
+ out.append(buf);
+ }
+ out.append("}");
+ }
+ } break;
+
+ case cppbor::TSTR:
+ out.append("'");
+ {
+ // TODO: escape "'" characters
+ out.append(item->asTstr()->value().c_str());
+ }
+ out.append("'");
+ break;
+
+ case cppbor::ARRAY: {
+ const cppbor::Array* array = item->asArray();
+ if (array->size() == 0) {
+ out.append("[]");
+ } else if (cborAreAllElementsNonCompound(array)) {
+ out.append("[");
+ for (size_t n = 0; n < array->size(); n++) {
+ if (!cborPrettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+ mapKeysToNotPrint)) {
+ return false;
+ }
+ out.append(", ");
+ }
+ out.append("]");
+ } else {
+ out.append("[\n" + indentString);
+ for (size_t n = 0; n < array->size(); n++) {
+ out.append(" ");
+ if (!cborPrettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+ mapKeysToNotPrint)) {
+ return false;
+ }
+ out.append(",\n" + indentString);
+ }
+ out.append("]");
+ }
+ } break;
+
+ case cppbor::MAP: {
+ const cppbor::Map* map = item->asMap();
+
+ if (map->size() == 0) {
+ out.append("{}");
+ } else {
+ out.append("{\n" + indentString);
+ for (size_t n = 0; n < map->size(); n++) {
+ out.append(" ");
+
+ auto [map_key, map_value] = (*map)[n];
+
+ if (!cborPrettyPrintInternal(map_key.get(), out, indent + 2, maxBStrSize,
+ mapKeysToNotPrint)) {
+ return false;
+ }
+ out.append(" : ");
+ if (map_key->type() == cppbor::TSTR &&
+ std::find(mapKeysToNotPrint.begin(), mapKeysToNotPrint.end(),
+ map_key->asTstr()->value()) != mapKeysToNotPrint.end()) {
+ out.append("<not printed>");
+ } else {
+ if (!cborPrettyPrintInternal(map_value.get(), out, indent + 2, maxBStrSize,
+ mapKeysToNotPrint)) {
+ return false;
+ }
+ }
+ out.append(",\n" + indentString);
+ }
+ out.append("}");
+ }
+ } break;
+
+ case cppbor::SEMANTIC: {
+ const cppbor::Semantic* semantic = item->asSemantic();
+ snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", semantic->value());
+ out.append(buf);
+ cborPrettyPrintInternal(semantic->child().get(), out, indent, maxBStrSize,
+ mapKeysToNotPrint);
+ } break;
+
+ case cppbor::SIMPLE:
+ const cppbor::Bool* asBool = item->asSimple()->asBool();
+ const cppbor::Null* asNull = item->asSimple()->asNull();
+ if (asBool != nullptr) {
+ out.append(asBool->value() ? "true" : "false");
+ } else if (asNull != nullptr) {
+ out.append("null");
+ } else {
+ LOG(ERROR) << "Only boolean/null is implemented for SIMPLE";
+ return false;
+ }
+ break;
+ }
+
+ return true;
+}
+
+string cborPrettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize,
+ const vector<string>& mapKeysToNotPrint) {
+ auto [item, _, message] = cppbor::parse(encodedCbor);
+ if (item == nullptr) {
+ LOG(ERROR) << "Data to pretty print is not valid CBOR: " << message;
+ return "";
+ }
+
+ string out;
+ cborPrettyPrintInternal(item.get(), out, 0, maxBStrSize, mapKeysToNotPrint);
+ return out;
+}
+
+// ---------------------------------------------------------------------------
+// Crypto functionality / abstraction.
+// ---------------------------------------------------------------------------
+
+struct EVP_CIPHER_CTX_Deleter {
+ void operator()(EVP_CIPHER_CTX* ctx) const {
+ if (ctx != nullptr) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
+ }
+};
+
+using EvpCipherCtxPtr = unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
+
+// bool getRandom(size_t numBytes, vector<uint8_t>& output) {
+optional<vector<uint8_t>> getRandom(size_t numBytes) {
+ vector<uint8_t> output;
+ output.resize(numBytes);
+ if (RAND_bytes(output.data(), numBytes) != 1) {
+ LOG(ERROR) << "RAND_bytes: failed getting " << numBytes << " random";
+ return {};
+ }
+ return output;
+}
+
+optional<vector<uint8_t>> decryptAes128Gcm(const vector<uint8_t>& key,
+ const vector<uint8_t>& encryptedData,
+ const vector<uint8_t>& additionalAuthenticatedData) {
+ int cipherTextSize = int(encryptedData.size()) - kAesGcmIvSize - kAesGcmTagSize;
+ if (cipherTextSize < 0) {
+ LOG(ERROR) << "encryptedData too small";
+ return {};
+ }
+ unsigned char* nonce = (unsigned char*)encryptedData.data();
+ unsigned char* cipherText = nonce + kAesGcmIvSize;
+ unsigned char* tag = cipherText + cipherTextSize;
+
+ vector<uint8_t> plainText;
+ plainText.resize(cipherTextSize);
+
+ auto ctx = EvpCipherCtxPtr(EVP_CIPHER_CTX_new());
+ if (ctx.get() == nullptr) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_new: failed";
+ return {};
+ }
+
+ if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
+ LOG(ERROR) << "EVP_DecryptInit_ex: failed";
+ return {};
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIvSize, NULL) != 1) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting nonce length";
+ return {};
+ }
+
+ if (EVP_DecryptInit_ex(ctx.get(), NULL, NULL, (unsigned char*)key.data(), nonce) != 1) {
+ LOG(ERROR) << "EVP_DecryptInit_ex: failed";
+ return {};
+ }
+
+ int numWritten;
+ if (additionalAuthenticatedData.size() > 0) {
+ if (EVP_DecryptUpdate(ctx.get(), NULL, &numWritten,
+ (unsigned char*)additionalAuthenticatedData.data(),
+ additionalAuthenticatedData.size()) != 1) {
+ LOG(ERROR) << "EVP_DecryptUpdate: failed for additionalAuthenticatedData";
+ return {};
+ }
+ if ((size_t)numWritten != additionalAuthenticatedData.size()) {
+ LOG(ERROR) << "EVP_DecryptUpdate: Unexpected outl=" << numWritten << " (expected "
+ << additionalAuthenticatedData.size() << ") for additionalAuthenticatedData";
+ return {};
+ }
+ }
+
+ if (EVP_DecryptUpdate(ctx.get(), (unsigned char*)plainText.data(), &numWritten, cipherText,
+ cipherTextSize) != 1) {
+ LOG(ERROR) << "EVP_DecryptUpdate: failed";
+ return {};
+ }
+ if (numWritten != cipherTextSize) {
+ LOG(ERROR) << "EVP_DecryptUpdate: Unexpected outl=" << numWritten << " (expected "
+ << cipherTextSize << ")";
+ return {};
+ }
+
+ if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag)) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting expected tag";
+ return {};
+ }
+
+ int ret = EVP_DecryptFinal_ex(ctx.get(), (unsigned char*)plainText.data() + numWritten,
+ &numWritten);
+ if (ret != 1) {
+ LOG(ERROR) << "EVP_DecryptFinal_ex: failed";
+ return {};
+ }
+ if (numWritten != 0) {
+ LOG(ERROR) << "EVP_DecryptFinal_ex: Unexpected non-zero outl=" << numWritten;
+ return {};
+ }
+
+ return plainText;
+}
+
+optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vector<uint8_t>& nonce,
+ const vector<uint8_t>& data,
+ const vector<uint8_t>& additionalAuthenticatedData) {
+ if (key.size() != kAes128GcmKeySize) {
+ LOG(ERROR) << "key is not kAes128GcmKeySize bytes";
+ return {};
+ }
+ if (nonce.size() != kAesGcmIvSize) {
+ LOG(ERROR) << "nonce is not kAesGcmIvSize bytes";
+ return {};
+ }
+
+ // The result is the nonce (kAesGcmIvSize bytes), the ciphertext, and
+ // finally the tag (kAesGcmTagSize bytes).
+ vector<uint8_t> encryptedData;
+ encryptedData.resize(data.size() + kAesGcmIvSize + kAesGcmTagSize);
+ unsigned char* noncePtr = (unsigned char*)encryptedData.data();
+ unsigned char* cipherText = noncePtr + kAesGcmIvSize;
+ unsigned char* tag = cipherText + data.size();
+ memcpy(noncePtr, nonce.data(), kAesGcmIvSize);
+
+ auto ctx = EvpCipherCtxPtr(EVP_CIPHER_CTX_new());
+ if (ctx.get() == nullptr) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_new: failed";
+ return {};
+ }
+
+ if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
+ LOG(ERROR) << "EVP_EncryptInit_ex: failed";
+ return {};
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIvSize, NULL) != 1) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting nonce length";
+ return {};
+ }
+
+ if (EVP_EncryptInit_ex(ctx.get(), NULL, NULL, (unsigned char*)key.data(),
+ (unsigned char*)nonce.data()) != 1) {
+ LOG(ERROR) << "EVP_EncryptInit_ex: failed";
+ return {};
+ }
+
+ int numWritten;
+ if (additionalAuthenticatedData.size() > 0) {
+ if (EVP_EncryptUpdate(ctx.get(), NULL, &numWritten,
+ (unsigned char*)additionalAuthenticatedData.data(),
+ additionalAuthenticatedData.size()) != 1) {
+ LOG(ERROR) << "EVP_EncryptUpdate: failed for additionalAuthenticatedData";
+ return {};
+ }
+ if ((size_t)numWritten != additionalAuthenticatedData.size()) {
+ LOG(ERROR) << "EVP_EncryptUpdate: Unexpected outl=" << numWritten << " (expected "
+ << additionalAuthenticatedData.size() << ") for additionalAuthenticatedData";
+ return {};
+ }
+ }
+
+ if (data.size() > 0) {
+ if (EVP_EncryptUpdate(ctx.get(), cipherText, &numWritten, (unsigned char*)data.data(),
+ data.size()) != 1) {
+ LOG(ERROR) << "EVP_EncryptUpdate: failed";
+ return {};
+ }
+ if ((size_t)numWritten != data.size()) {
+ LOG(ERROR) << "EVP_EncryptUpdate: Unexpected outl=" << numWritten << " (expected "
+ << data.size() << ")";
+ return {};
+ }
+ }
+
+ if (EVP_EncryptFinal_ex(ctx.get(), cipherText + numWritten, &numWritten) != 1) {
+ LOG(ERROR) << "EVP_EncryptFinal_ex: failed";
+ return {};
+ }
+ if (numWritten != 0) {
+ LOG(ERROR) << "EVP_EncryptFinal_ex: Unexpected non-zero outl=" << numWritten;
+ return {};
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, tag) != 1) {
+ LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed getting tag";
+ return {};
+ }
+
+ return encryptedData;
+}
+
+struct EC_KEY_Deleter {
+ void operator()(EC_KEY* key) const {
+ if (key != nullptr) {
+ EC_KEY_free(key);
+ }
+ }
+};
+using EC_KEY_Ptr = unique_ptr<EC_KEY, EC_KEY_Deleter>;
+
+struct EVP_PKEY_Deleter {
+ void operator()(EVP_PKEY* key) const {
+ if (key != nullptr) {
+ EVP_PKEY_free(key);
+ }
+ }
+};
+using EVP_PKEY_Ptr = unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
+
+struct EVP_PKEY_CTX_Deleter {
+ void operator()(EVP_PKEY_CTX* ctx) const {
+ if (ctx != nullptr) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ }
+};
+using EVP_PKEY_CTX_Ptr = unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_Deleter>;
+
+struct EC_GROUP_Deleter {
+ void operator()(EC_GROUP* group) const {
+ if (group != nullptr) {
+ EC_GROUP_free(group);
+ }
+ }
+};
+using EC_GROUP_Ptr = unique_ptr<EC_GROUP, EC_GROUP_Deleter>;
+
+struct EC_POINT_Deleter {
+ void operator()(EC_POINT* point) const {
+ if (point != nullptr) {
+ EC_POINT_free(point);
+ }
+ }
+};
+
+using EC_POINT_Ptr = unique_ptr<EC_POINT, EC_POINT_Deleter>;
+
+struct ECDSA_SIG_Deleter {
+ void operator()(ECDSA_SIG* sig) const {
+ if (sig != nullptr) {
+ ECDSA_SIG_free(sig);
+ }
+ }
+};
+using ECDSA_SIG_Ptr = unique_ptr<ECDSA_SIG, ECDSA_SIG_Deleter>;
+
+struct X509_Deleter {
+ void operator()(X509* x509) const {
+ if (x509 != nullptr) {
+ X509_free(x509);
+ }
+ }
+};
+using X509_Ptr = unique_ptr<X509, X509_Deleter>;
+
+struct PKCS12_Deleter {
+ void operator()(PKCS12* pkcs12) const {
+ if (pkcs12 != nullptr) {
+ PKCS12_free(pkcs12);
+ }
+ }
+};
+using PKCS12_Ptr = unique_ptr<PKCS12, PKCS12_Deleter>;
+
+struct BIGNUM_Deleter {
+ void operator()(BIGNUM* bignum) const {
+ if (bignum != nullptr) {
+ BN_free(bignum);
+ }
+ }
+};
+using BIGNUM_Ptr = unique_ptr<BIGNUM, BIGNUM_Deleter>;
+
+struct ASN1_INTEGER_Deleter {
+ void operator()(ASN1_INTEGER* value) const {
+ if (value != nullptr) {
+ ASN1_INTEGER_free(value);
+ }
+ }
+};
+using ASN1_INTEGER_Ptr = unique_ptr<ASN1_INTEGER, ASN1_INTEGER_Deleter>;
+
+struct ASN1_TIME_Deleter {
+ void operator()(ASN1_TIME* value) const {
+ if (value != nullptr) {
+ ASN1_TIME_free(value);
+ }
+ }
+};
+using ASN1_TIME_Ptr = unique_ptr<ASN1_TIME, ASN1_TIME_Deleter>;
+
+struct X509_NAME_Deleter {
+ void operator()(X509_NAME* value) const {
+ if (value != nullptr) {
+ X509_NAME_free(value);
+ }
+ }
+};
+using X509_NAME_Ptr = unique_ptr<X509_NAME, X509_NAME_Deleter>;
+
+vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain) {
+ vector<uint8_t> ret;
+ for (const vector<uint8_t>& certificate : certificateChain) {
+ ret.insert(ret.end(), certificate.begin(), certificate.end());
+ }
+ return ret;
+}
+
+optional<vector<vector<uint8_t>>> certificateChainSplit(const vector<uint8_t>& certificateChain) {
+ const unsigned char* pStart = (unsigned char*)certificateChain.data();
+ const unsigned char* p = pStart;
+ const unsigned char* pEnd = p + certificateChain.size();
+ vector<vector<uint8_t>> certificates;
+ while (p < pEnd) {
+ size_t begin = p - pStart;
+ auto x509 = X509_Ptr(d2i_X509(nullptr, &p, pEnd - p));
+ size_t next = p - pStart;
+ if (x509 == nullptr) {
+ LOG(ERROR) << "Error parsing X509 certificate";
+ return {};
+ }
+ vector<uint8_t> cert =
+ vector<uint8_t>(certificateChain.begin() + begin, certificateChain.begin() + next);
+ certificates.push_back(std::move(cert));
+ }
+ return certificates;
+}
+
+static bool parseX509Certificates(const vector<uint8_t>& certificateChain,
+ vector<X509_Ptr>& parsedCertificates) {
+ const unsigned char* p = (unsigned char*)certificateChain.data();
+ const unsigned char* pEnd = p + certificateChain.size();
+ parsedCertificates.resize(0);
+ while (p < pEnd) {
+ auto x509 = X509_Ptr(d2i_X509(nullptr, &p, pEnd - p));
+ if (x509 == nullptr) {
+ LOG(ERROR) << "Error parsing X509 certificate";
+ return false;
+ }
+ parsedCertificates.push_back(std::move(x509));
+ }
+ return true;
+}
+
+// TODO: Right now the only check we perform is to check that each certificate
+// is signed by its successor. We should - but currently don't - also check
+// things like valid dates etc.
+//
+// It would be nice to use X509_verify_cert() instead of doing our own thing.
+//
+bool certificateChainValidate(const vector<uint8_t>& certificateChain) {
+ vector<X509_Ptr> certs;
+
+ if (!parseX509Certificates(certificateChain, certs)) {
+ LOG(ERROR) << "Error parsing X509 certificates";
+ return false;
+ }
+
+ if (certs.size() == 1) {
+ return true;
+ }
+
+ for (size_t n = 1; n < certs.size(); n++) {
+ const X509_Ptr& keyCert = certs[n - 1];
+ const X509_Ptr& signingCert = certs[n];
+ EVP_PKEY_Ptr signingPubkey(X509_get_pubkey(signingCert.get()));
+ if (X509_verify(keyCert.get(), signingPubkey.get()) != 1) {
+ LOG(ERROR) << "Error validating cert at index " << n - 1
+ << " is signed by its successor";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool checkEcDsaSignature(const vector<uint8_t>& digest, const vector<uint8_t>& signature,
+ const vector<uint8_t>& publicKey) {
+ const unsigned char* p = (unsigned char*)signature.data();
+ auto sig = ECDSA_SIG_Ptr(d2i_ECDSA_SIG(nullptr, &p, signature.size()));
+ if (sig.get() == nullptr) {
+ LOG(ERROR) << "Error decoding DER encoded signature";
+ return false;
+ }
+
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+ if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+ 1) {
+ LOG(ERROR) << "Error decoding publicKey";
+ return false;
+ }
+ auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return false;
+ }
+ if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+ LOG(ERROR) << "Error setting group";
+ return false;
+ }
+ if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+ LOG(ERROR) << "Error setting point";
+ return false;
+ }
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+ LOG(ERROR) << "Error setting key";
+ return false;
+ }
+
+ int rc = ECDSA_do_verify(digest.data(), digest.size(), sig.get(), ecKey.get());
+ if (rc != 1) {
+ LOG(ERROR) << "Error verifying signature (rc=" << rc << ")";
+ return false;
+ }
+
+ return true;
+}
+
+vector<uint8_t> sha256(const vector<uint8_t>& data) {
+ vector<uint8_t> ret;
+ ret.resize(SHA256_DIGEST_LENGTH);
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, data.data(), data.size());
+ SHA256_Final((unsigned char*)ret.data(), &ctx);
+ return ret;
+}
+
+optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data) {
+ auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr));
+ if (bn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM";
+ return {};
+ }
+
+ auto ec_key = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ if (EC_KEY_set_private_key(ec_key.get(), bn.get()) != 1) {
+ LOG(ERROR) << "Error setting private key from BIGNUM";
+ return {};
+ }
+
+ auto digest = sha256(data);
+ ECDSA_SIG* sig = ECDSA_do_sign(digest.data(), digest.size(), ec_key.get());
+ if (sig == nullptr) {
+ LOG(ERROR) << "Error signing digest";
+ return {};
+ }
+ size_t len = i2d_ECDSA_SIG(sig, nullptr);
+ vector<uint8_t> signature;
+ signature.resize(len);
+ unsigned char* p = (unsigned char*)signature.data();
+ i2d_ECDSA_SIG(sig, &p);
+ ECDSA_SIG_free(sig);
+ return signature;
+}
+
+optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<uint8_t>& data) {
+ HMAC_CTX ctx;
+ HMAC_CTX_init(&ctx);
+ if (HMAC_Init_ex(&ctx, key.data(), key.size(), EVP_sha256(), nullptr /* impl */) != 1) {
+ LOG(ERROR) << "Error initializing HMAC_CTX";
+ return {};
+ }
+ if (HMAC_Update(&ctx, data.data(), data.size()) != 1) {
+ LOG(ERROR) << "Error updating HMAC_CTX";
+ return {};
+ }
+ vector<uint8_t> hmac;
+ hmac.resize(32);
+ unsigned int size = 0;
+ if (HMAC_Final(&ctx, hmac.data(), &size) != 1) {
+ LOG(ERROR) << "Error finalizing HMAC_CTX";
+ return {};
+ }
+ if (size != 32) {
+ LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+ return {};
+ }
+ return hmac;
+}
+
+optional<vector<uint8_t>> createEcKeyPair() {
+ auto ec_key = EC_KEY_Ptr(EC_KEY_new());
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ if (ec_key.get() == nullptr || pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return {};
+ }
+
+ if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
+ EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
+ LOG(ERROR) << "Error generating key";
+ return {};
+ }
+
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()) != 1) {
+ LOG(ERROR) << "Error getting private key";
+ return {};
+ }
+
+ int size = i2d_PrivateKey(pkey.get(), nullptr);
+ if (size == 0) {
+ LOG(ERROR) << "Error generating public key encoding";
+ return {};
+ }
+ vector<uint8_t> keyPair;
+ keyPair.resize(size);
+ unsigned char* p = keyPair.data();
+ i2d_PrivateKey(pkey.get(), &p);
+ return keyPair;
+}
+
+optional<vector<uint8_t>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair) {
+ const unsigned char* p = (const unsigned char*)keyPair.data();
+ auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+ if (pkey.get() == nullptr) {
+ LOG(ERROR) << "Error parsing keyPair";
+ return {};
+ }
+
+ auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+ if (ecKey.get() == nullptr) {
+ LOG(ERROR) << "Failed getting EC key";
+ return {};
+ }
+
+ auto ecGroup = EC_KEY_get0_group(ecKey.get());
+ auto ecPoint = EC_KEY_get0_public_key(ecKey.get());
+ int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0,
+ nullptr);
+ if (size == 0) {
+ LOG(ERROR) << "Error generating public key encoding";
+ return {};
+ }
+
+ vector<uint8_t> publicKey;
+ publicKey.resize(size);
+ EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(),
+ publicKey.size(), nullptr);
+ return publicKey;
+}
+
+optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair) {
+ const unsigned char* p = (const unsigned char*)keyPair.data();
+ auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+ if (pkey.get() == nullptr) {
+ LOG(ERROR) << "Error parsing keyPair";
+ return {};
+ }
+
+ auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+ if (ecKey.get() == nullptr) {
+ LOG(ERROR) << "Failed getting EC key";
+ return {};
+ }
+
+ const BIGNUM* bignum = EC_KEY_get0_private_key(ecKey.get());
+ if (bignum == nullptr) {
+ LOG(ERROR) << "Error getting bignum from private key";
+ return {};
+ }
+ vector<uint8_t> privateKey;
+ privateKey.resize(BN_num_bytes(bignum));
+ BN_bn2bin(bignum, privateKey.data());
+ return privateKey;
+}
+
+optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, const string& name,
+ const string& serialDecimal, const string& issuer,
+ const string& subject, time_t validityNotBefore,
+ time_t validityNotAfter) {
+ const unsigned char* p = (const unsigned char*)keyPair.data();
+ auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+ if (pkey.get() == nullptr) {
+ LOG(ERROR) << "Error parsing keyPair";
+ return {};
+ }
+
+ auto x509 = X509_Ptr(X509_new());
+ if (!x509.get()) {
+ LOG(ERROR) << "Error creating X509 certificate";
+ return {};
+ }
+
+ if (!X509_set_version(x509.get(), 2 /* version 3, but zero-based */)) {
+ LOG(ERROR) << "Error setting version to 3";
+ return {};
+ }
+
+ if (X509_set_pubkey(x509.get(), pkey.get()) != 1) {
+ LOG(ERROR) << "Error setting public key";
+ return {};
+ }
+
+ BIGNUM* bignumSerial = nullptr;
+ if (BN_dec2bn(&bignumSerial, serialDecimal.c_str()) == 0) {
+ LOG(ERROR) << "Error parsing serial";
+ return {};
+ }
+ auto bignumSerialPtr = BIGNUM_Ptr(bignumSerial);
+ auto asnSerial = ASN1_INTEGER_Ptr(BN_to_ASN1_INTEGER(bignumSerial, nullptr));
+ if (X509_set_serialNumber(x509.get(), asnSerial.get()) != 1) {
+ LOG(ERROR) << "Error setting serial";
+ return {};
+ }
+
+ auto x509Issuer = X509_NAME_Ptr(X509_NAME_new());
+ if (x509Issuer.get() == nullptr ||
+ X509_NAME_add_entry_by_txt(x509Issuer.get(), "CN", MBSTRING_ASC,
+ (const uint8_t*)issuer.c_str(), issuer.size(), -1 /* loc */,
+ 0 /* set */) != 1 ||
+ X509_set_issuer_name(x509.get(), x509Issuer.get()) != 1) {
+ LOG(ERROR) << "Error setting issuer";
+ return {};
+ }
+
+ auto x509Subject = X509_NAME_Ptr(X509_NAME_new());
+ if (x509Subject.get() == nullptr ||
+ X509_NAME_add_entry_by_txt(x509Subject.get(), "CN", MBSTRING_ASC,
+ (const uint8_t*)subject.c_str(), subject.size(), -1 /* loc */,
+ 0 /* set */) != 1 ||
+ X509_set_subject_name(x509.get(), x509Subject.get()) != 1) {
+ LOG(ERROR) << "Error setting subject";
+ return {};
+ }
+
+ auto asnNotBefore = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotBefore));
+ if (asnNotBefore.get() == nullptr || X509_set_notBefore(x509.get(), asnNotBefore.get()) != 1) {
+ LOG(ERROR) << "Error setting notBefore";
+ return {};
+ }
+
+ auto asnNotAfter = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotAfter));
+ if (asnNotAfter.get() == nullptr || X509_set_notAfter(x509.get(), asnNotAfter.get()) != 1) {
+ LOG(ERROR) << "Error setting notAfter";
+ return {};
+ }
+
+ if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) == 0) {
+ LOG(ERROR) << "Error signing X509 certificate";
+ return {};
+ }
+
+ // Ideally we wouldn't encrypt it (we're only using this function for
+ // sending a key-pair over binder to the Android app) but BoringSSL does not
+ // support this: from pkcs8_x509.c in BoringSSL: "In OpenSSL, -1 here means
+ // to use no encryption, which we do not currently support."
+ //
+ // Passing nullptr as |pass|, though, means "no password". So we'll do that.
+ // Compare with the receiving side - CredstoreIdentityCredential.java - where
+ // an empty char[] is passed as the password.
+ //
+ auto pkcs12 = PKCS12_Ptr(PKCS12_create(nullptr, name.c_str(), pkey.get(), x509.get(),
+ nullptr, // ca
+ 0, // nid_key
+ 0, // nid_cert
+ 0, // iter,
+ 0, // mac_iter,
+ 0)); // keytype
+ if (pkcs12.get() == nullptr) {
+ char buf[128];
+ long errCode = ERR_get_error();
+ ERR_error_string_n(errCode, buf, sizeof buf);
+ LOG(ERROR) << "Error creating PKCS12, code " << errCode << ": " << buf;
+ return {};
+ }
+
+ unsigned char* buffer = nullptr;
+ int length = i2d_PKCS12(pkcs12.get(), &buffer);
+ if (length < 0) {
+ LOG(ERROR) << "Error encoding PKCS12";
+ return {};
+ }
+ vector<uint8_t> pkcs12Bytes;
+ pkcs12Bytes.resize(length);
+ memcpy(pkcs12Bytes.data(), buffer, length);
+ OPENSSL_free(buffer);
+
+ return pkcs12Bytes;
+}
+
+optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
+ const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
+ const string& serialDecimal, const string& issuer, const string& subject,
+ time_t validityNotBefore, time_t validityNotAfter) {
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+ if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+ 1) {
+ LOG(ERROR) << "Error decoding publicKey";
+ return {};
+ }
+ auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return {};
+ }
+ if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+ LOG(ERROR) << "Error setting group";
+ return {};
+ }
+ if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+ LOG(ERROR) << "Error setting point";
+ return {};
+ }
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+ LOG(ERROR) << "Error setting key";
+ return {};
+ }
+
+ auto bn = BIGNUM_Ptr(BN_bin2bn(signingKey.data(), signingKey.size(), nullptr));
+ if (bn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM for private key";
+ return {};
+ }
+ auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) {
+ LOG(ERROR) << "Error setting private key from BIGNUM";
+ return {};
+ }
+ auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) {
+ LOG(ERROR) << "Error setting private key";
+ return {};
+ }
+
+ auto x509 = X509_Ptr(X509_new());
+ if (!x509.get()) {
+ LOG(ERROR) << "Error creating X509 certificate";
+ return {};
+ }
+
+ if (!X509_set_version(x509.get(), 2 /* version 3, but zero-based */)) {
+ LOG(ERROR) << "Error setting version to 3";
+ return {};
+ }
+
+ if (X509_set_pubkey(x509.get(), pkey.get()) != 1) {
+ LOG(ERROR) << "Error setting public key";
+ return {};
+ }
+
+ BIGNUM* bignumSerial = nullptr;
+ if (BN_dec2bn(&bignumSerial, serialDecimal.c_str()) == 0) {
+ LOG(ERROR) << "Error parsing serial";
+ return {};
+ }
+ auto bignumSerialPtr = BIGNUM_Ptr(bignumSerial);
+ auto asnSerial = ASN1_INTEGER_Ptr(BN_to_ASN1_INTEGER(bignumSerial, nullptr));
+ if (X509_set_serialNumber(x509.get(), asnSerial.get()) != 1) {
+ LOG(ERROR) << "Error setting serial";
+ return {};
+ }
+
+ auto x509Issuer = X509_NAME_Ptr(X509_NAME_new());
+ if (x509Issuer.get() == nullptr ||
+ X509_NAME_add_entry_by_txt(x509Issuer.get(), "CN", MBSTRING_ASC,
+ (const uint8_t*)issuer.c_str(), issuer.size(), -1 /* loc */,
+ 0 /* set */) != 1 ||
+ X509_set_issuer_name(x509.get(), x509Issuer.get()) != 1) {
+ LOG(ERROR) << "Error setting issuer";
+ return {};
+ }
+
+ auto x509Subject = X509_NAME_Ptr(X509_NAME_new());
+ if (x509Subject.get() == nullptr ||
+ X509_NAME_add_entry_by_txt(x509Subject.get(), "CN", MBSTRING_ASC,
+ (const uint8_t*)subject.c_str(), subject.size(), -1 /* loc */,
+ 0 /* set */) != 1 ||
+ X509_set_subject_name(x509.get(), x509Subject.get()) != 1) {
+ LOG(ERROR) << "Error setting subject";
+ return {};
+ }
+
+ auto asnNotBefore = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotBefore));
+ if (asnNotBefore.get() == nullptr || X509_set_notBefore(x509.get(), asnNotBefore.get()) != 1) {
+ LOG(ERROR) << "Error setting notBefore";
+ return {};
+ }
+
+ auto asnNotAfter = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotAfter));
+ if (asnNotAfter.get() == nullptr || X509_set_notAfter(x509.get(), asnNotAfter.get()) != 1) {
+ LOG(ERROR) << "Error setting notAfter";
+ return {};
+ }
+
+ if (X509_sign(x509.get(), privPkey.get(), EVP_sha256()) == 0) {
+ LOG(ERROR) << "Error signing X509 certificate";
+ return {};
+ }
+
+ unsigned char* buffer = nullptr;
+ int length = i2d_X509(x509.get(), &buffer);
+ if (length < 0) {
+ LOG(ERROR) << "Error DER encoding X509 certificate";
+ return {};
+ }
+
+ vector<uint8_t> certificate;
+ certificate.resize(length);
+ memcpy(certificate.data(), buffer, length);
+ OPENSSL_free(buffer);
+ return certificate;
+}
+
+optional<vector<uint8_t>> ecdh(const vector<uint8_t>& publicKey,
+ const vector<uint8_t>& privateKey) {
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+ if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+ 1) {
+ LOG(ERROR) << "Error decoding publicKey";
+ return {};
+ }
+ auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return {};
+ }
+ if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+ LOG(ERROR) << "Error setting group";
+ return {};
+ }
+ if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+ LOG(ERROR) << "Error setting point";
+ return {};
+ }
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+ LOG(ERROR) << "Error setting key";
+ return {};
+ }
+
+ auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr));
+ if (bn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM for private key";
+ return {};
+ }
+ auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) {
+ LOG(ERROR) << "Error setting private key from BIGNUM";
+ return {};
+ }
+ auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) {
+ LOG(ERROR) << "Error setting private key";
+ return {};
+ }
+
+ auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(privPkey.get(), NULL));
+ if (ctx.get() == nullptr) {
+ LOG(ERROR) << "Error creating context";
+ return {};
+ }
+
+ if (EVP_PKEY_derive_init(ctx.get()) != 1) {
+ LOG(ERROR) << "Error initializing context";
+ return {};
+ }
+
+ if (EVP_PKEY_derive_set_peer(ctx.get(), pkey.get()) != 1) {
+ LOG(ERROR) << "Error setting peer";
+ return {};
+ }
+
+ /* Determine buffer length for shared secret */
+ size_t secretLen = 0;
+ if (EVP_PKEY_derive(ctx.get(), NULL, &secretLen) != 1) {
+ LOG(ERROR) << "Error determing length of shared secret";
+ return {};
+ }
+ vector<uint8_t> sharedSecret;
+ sharedSecret.resize(secretLen);
+
+ if (EVP_PKEY_derive(ctx.get(), sharedSecret.data(), &secretLen) != 1) {
+ LOG(ERROR) << "Error deriving shared secret";
+ return {};
+ }
+ return sharedSecret;
+}
+
+optional<vector<uint8_t>> hkdf(const vector<uint8_t>& sharedSecret, const vector<uint8_t>& salt,
+ const vector<uint8_t>& info, size_t size) {
+ vector<uint8_t> derivedKey;
+ derivedKey.resize(size);
+ if (HKDF(derivedKey.data(), derivedKey.size(), EVP_sha256(), sharedSecret.data(),
+ sharedSecret.size(), salt.data(), salt.size(), info.data(), info.size()) != 1) {
+ LOG(ERROR) << "Error deriving key";
+ return {};
+ }
+ return derivedKey;
+}
+
+void removeLeadingZeroes(vector<uint8_t>& vec) {
+ while (vec.size() >= 1 && vec[0] == 0x00) {
+ vec.erase(vec.begin());
+ }
+}
+
+tuple<bool, vector<uint8_t>, vector<uint8_t>> ecPublicKeyGetXandY(
+ const vector<uint8_t>& publicKey) {
+ if (publicKey.size() != 65 || publicKey[0] != 0x04) {
+ LOG(ERROR) << "publicKey is not in the expected format";
+ return std::make_tuple(false, vector<uint8_t>(), vector<uint8_t>());
+ }
+ vector<uint8_t> x, y;
+ x.resize(32);
+ y.resize(32);
+ memcpy(x.data(), publicKey.data() + 1, 32);
+ memcpy(y.data(), publicKey.data() + 33, 32);
+
+ removeLeadingZeroes(x);
+ removeLeadingZeroes(y);
+
+ return std::make_tuple(true, x, y);
+}
+
+optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& certificateChain) {
+ vector<X509_Ptr> certs;
+ if (!parseX509Certificates(certificateChain, certs)) {
+ return {};
+ }
+ if (certs.size() < 1) {
+ LOG(ERROR) << "No certificates in chain";
+ return {};
+ }
+
+ int algoId = OBJ_obj2nid(certs[0]->cert_info->key->algor->algorithm);
+ if (algoId != NID_X9_62_id_ecPublicKey) {
+ LOG(ERROR) << "Expected NID_X9_62_id_ecPublicKey, got " << OBJ_nid2ln(algoId);
+ return {};
+ }
+
+ auto pkey = EVP_PKEY_Ptr(X509_get_pubkey(certs[0].get()));
+ if (pkey.get() == nullptr) {
+ LOG(ERROR) << "No public key";
+ return {};
+ }
+
+ auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+ if (ecKey.get() == nullptr) {
+ LOG(ERROR) << "Failed getting EC key";
+ return {};
+ }
+
+ auto ecGroup = EC_KEY_get0_group(ecKey.get());
+ auto ecPoint = EC_KEY_get0_public_key(ecKey.get());
+ int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0,
+ nullptr);
+ if (size == 0) {
+ LOG(ERROR) << "Error generating public key encoding";
+ return {};
+ }
+ vector<uint8_t> publicKey;
+ publicKey.resize(size);
+ EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(),
+ publicKey.size(), nullptr);
+ return publicKey;
+}
+
+// ---------------------------------------------------------------------------
+// COSE Utility Functions
+// ---------------------------------------------------------------------------
+
+vector<uint8_t> coseBuildToBeSigned(const vector<uint8_t>& encodedProtectedHeaders,
+ const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent) {
+ cppbor::Array sigStructure;
+ sigStructure.add("Signature1");
+ sigStructure.add(encodedProtectedHeaders);
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ vector<uint8_t> emptyExternalAad;
+ sigStructure.add(emptyExternalAad);
+
+ // Next field is the payload, independently of how it's transported (RFC
+ // 8152 section 4.4). Since our API specifies only one of |data| and
+ // |detachedContent| can be non-empty, it's simply just the non-empty one.
+ if (data.size() > 0) {
+ sigStructure.add(data);
+ } else {
+ sigStructure.add(detachedContent);
+ }
+ return sigStructure.encode();
+}
+
+vector<uint8_t> coseEncodeHeaders(const cppbor::Map& protectedHeaders) {
+ if (protectedHeaders.size() == 0) {
+ cppbor::Bstr emptyBstr(vector<uint8_t>({}));
+ return emptyBstr.encode();
+ }
+ return protectedHeaders.encode();
+}
+
+// From https://tools.ietf.org/html/rfc8152
+const int COSE_LABEL_ALG = 1;
+const int COSE_LABEL_X5CHAIN = 33; // temporary identifier
+
+// From "COSE Algorithms" registry
+const int COSE_ALG_ECDSA_256 = -7;
+const int COSE_ALG_HMAC_256_256 = 5;
+
+bool ecdsaSignatureCoseToDer(const vector<uint8_t>& ecdsaCoseSignature,
+ vector<uint8_t>& ecdsaDerSignature) {
+ if (ecdsaCoseSignature.size() != 64) {
+ LOG(ERROR) << "COSE signature length is " << ecdsaCoseSignature.size() << ", expected 64";
+ return false;
+ }
+
+ auto rBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data(), 32, nullptr));
+ if (rBn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM for r";
+ return false;
+ }
+
+ auto sBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data() + 32, 32, nullptr));
+ if (sBn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM for s";
+ return false;
+ }
+
+ ECDSA_SIG sig;
+ sig.r = rBn.get();
+ sig.s = sBn.get();
+
+ size_t len = i2d_ECDSA_SIG(&sig, nullptr);
+ ecdsaDerSignature.resize(len);
+ unsigned char* p = (unsigned char*)ecdsaDerSignature.data();
+ i2d_ECDSA_SIG(&sig, &p);
+
+ return true;
+}
+
+bool ecdsaSignatureDerToCose(const vector<uint8_t>& ecdsaDerSignature,
+ vector<uint8_t>& ecdsaCoseSignature) {
+ ECDSA_SIG* sig;
+ const unsigned char* p = ecdsaDerSignature.data();
+ sig = d2i_ECDSA_SIG(nullptr, &p, ecdsaDerSignature.size());
+ if (sig == nullptr) {
+ LOG(ERROR) << "Error decoding DER signature";
+ return false;
+ }
+
+ ecdsaCoseSignature.clear();
+ ecdsaCoseSignature.resize(64);
+ if (BN_bn2binpad(sig->r, ecdsaCoseSignature.data(), 32) != 32) {
+ LOG(ERROR) << "Error encoding r";
+ return false;
+ }
+ if (BN_bn2binpad(sig->s, ecdsaCoseSignature.data() + 32, 32) != 32) {
+ LOG(ERROR) << "Error encoding s";
+ return false;
+ }
+ return true;
+}
+
+optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent,
+ const vector<uint8_t>& certificateChain) {
+ cppbor::Map unprotectedHeaders;
+ cppbor::Map protectedHeaders;
+
+ if (data.size() > 0 && detachedContent.size() > 0) {
+ LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+ return {};
+ }
+
+ protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
+
+ if (certificateChain.size() != 0) {
+ optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certificateChain);
+ if (!certs) {
+ LOG(ERROR) << "Error splitting certificate chain";
+ return {};
+ }
+ if (certs.value().size() == 1) {
+ unprotectedHeaders.add(COSE_LABEL_X5CHAIN, certs.value()[0]);
+ } else {
+ cppbor::Array certArray;
+ for (const vector<uint8_t>& cert : certs.value()) {
+ certArray.add(cert);
+ }
+ unprotectedHeaders.add(COSE_LABEL_X5CHAIN, std::move(certArray));
+ }
+ }
+
+ vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);
+ vector<uint8_t> toBeSigned =
+ coseBuildToBeSigned(encodedProtectedHeaders, data, detachedContent);
+
+ optional<vector<uint8_t>> derSignature = signEcDsa(key, toBeSigned);
+ if (!derSignature) {
+ LOG(ERROR) << "Error signing toBeSigned data";
+ return {};
+ }
+ vector<uint8_t> coseSignature;
+ if (!ecdsaSignatureDerToCose(derSignature.value(), coseSignature)) {
+ LOG(ERROR) << "Error converting ECDSA signature from DER to COSE format";
+ return {};
+ }
+
+ cppbor::Array coseSign1;
+ coseSign1.add(encodedProtectedHeaders);
+ coseSign1.add(std::move(unprotectedHeaders));
+ if (data.size() == 0) {
+ cppbor::Null nullValue;
+ coseSign1.add(std::move(nullValue));
+ } else {
+ coseSign1.add(data);
+ }
+ coseSign1.add(coseSignature);
+ vector<uint8_t> signatureCoseSign1;
+ signatureCoseSign1 = coseSign1.encode();
+ return signatureCoseSign1;
+}
+
+bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
+ const vector<uint8_t>& detachedContent,
+ const vector<uint8_t>& publicKey) {
+ auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+ if (item == nullptr) {
+ LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+ return false;
+ }
+ const cppbor::Array* array = item->asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+ return false;
+ }
+ if (array->size() != 4) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+ return false;
+ }
+
+ const cppbor::Bstr* encodedProtectedHeadersBstr = (*array)[0]->asBstr();
+ ;
+ if (encodedProtectedHeadersBstr == nullptr) {
+ LOG(ERROR) << "Value for encodedProtectedHeaders is not a bstr";
+ return false;
+ }
+ const vector<uint8_t> encodedProtectedHeaders = encodedProtectedHeadersBstr->value();
+
+ const cppbor::Map* unprotectedHeaders = (*array)[1]->asMap();
+ if (unprotectedHeaders == nullptr) {
+ LOG(ERROR) << "Value for unprotectedHeaders is not a map";
+ return false;
+ }
+
+ vector<uint8_t> data;
+ const cppbor::Simple* payloadAsSimple = (*array)[2]->asSimple();
+ if (payloadAsSimple != nullptr) {
+ if (payloadAsSimple->asNull() == nullptr) {
+ LOG(ERROR) << "Value for payload is not null or a bstr";
+ return false;
+ }
+ } else {
+ const cppbor::Bstr* payloadAsBstr = (*array)[2]->asBstr();
+ if (payloadAsBstr == nullptr) {
+ LOG(ERROR) << "Value for payload is not null or a bstr";
+ return false;
+ }
+ data = payloadAsBstr->value(); // TODO: avoid copy
+ }
+
+ if (data.size() > 0 && detachedContent.size() > 0) {
+ LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+ return false;
+ }
+
+ const cppbor::Bstr* signatureBstr = (*array)[3]->asBstr();
+ if (signatureBstr == nullptr) {
+ LOG(ERROR) << "Value for signature is a bstr";
+ return false;
+ }
+ const vector<uint8_t>& coseSignature = signatureBstr->value();
+
+ vector<uint8_t> derSignature;
+ if (!ecdsaSignatureCoseToDer(coseSignature, derSignature)) {
+ LOG(ERROR) << "Error converting ECDSA signature from COSE to DER format";
+ return false;
+ }
+
+ vector<uint8_t> toBeSigned =
+ coseBuildToBeSigned(encodedProtectedHeaders, data, detachedContent);
+ if (!checkEcDsaSignature(support::sha256(toBeSigned), derSignature, publicKey)) {
+ LOG(ERROR) << "Signature check failed";
+ return false;
+ }
+ return true;
+}
+
+optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1) {
+ auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+ if (item == nullptr) {
+ LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+ return {};
+ }
+ const cppbor::Array* array = item->asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+ return {};
+ }
+ if (array->size() != 4) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+ return {};
+ }
+
+ vector<uint8_t> data;
+ const cppbor::Simple* payloadAsSimple = (*array)[2]->asSimple();
+ if (payloadAsSimple != nullptr) {
+ if (payloadAsSimple->asNull() == nullptr) {
+ LOG(ERROR) << "Value for payload is not null or a bstr";
+ return {};
+ }
+ // payload is null, so |data| should be empty (as it is)
+ } else {
+ const cppbor::Bstr* payloadAsBstr = (*array)[2]->asBstr();
+ if (payloadAsBstr == nullptr) {
+ LOG(ERROR) << "Value for payload is not null or a bstr";
+ return {};
+ }
+ // Copy payload into |data|
+ data = payloadAsBstr->value();
+ }
+
+ return data;
+}
+
+optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCoseSign1) {
+ auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+ if (item == nullptr) {
+ LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+ return {};
+ }
+ const cppbor::Array* array = item->asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+ return {};
+ }
+ if (array->size() != 4) {
+ LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+ return {};
+ }
+
+ const cppbor::Map* unprotectedHeaders = (*array)[1]->asMap();
+ if (unprotectedHeaders == nullptr) {
+ LOG(ERROR) << "Value for unprotectedHeaders is not a map";
+ return {};
+ }
+
+ for (size_t n = 0; n < unprotectedHeaders->size(); n++) {
+ auto [keyItem, valueItem] = (*unprotectedHeaders)[n];
+ const cppbor::Int* number = keyItem->asInt();
+ if (number == nullptr) {
+ LOG(ERROR) << "Key item in top-level map is not a number";
+ return {};
+ }
+ int label = number->value();
+ if (label == COSE_LABEL_X5CHAIN) {
+ const cppbor::Bstr* bstr = valueItem->asBstr();
+ if (bstr != nullptr) {
+ return bstr->value();
+ }
+ const cppbor::Array* array = valueItem->asArray();
+ if (array != nullptr) {
+ vector<uint8_t> certs;
+ for (size_t m = 0; m < array->size(); m++) {
+ const cppbor::Bstr* bstr = ((*array)[m])->asBstr();
+ if (bstr == nullptr) {
+ LOG(ERROR) << "Item in x5chain array is not a bstr";
+ return {};
+ }
+ const vector<uint8_t>& certValue = bstr->value();
+ certs.insert(certs.end(), certValue.begin(), certValue.end());
+ }
+ return certs;
+ }
+ LOG(ERROR) << "Value for x5chain label is not a bstr or array";
+ return {};
+ }
+ }
+ LOG(ERROR) << "Did not find x5chain label in unprotected headers";
+ return {};
+}
+
+vector<uint8_t> coseBuildToBeMACed(const vector<uint8_t>& encodedProtectedHeaders,
+ const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent) {
+ cppbor::Array macStructure;
+ macStructure.add("MAC0");
+ macStructure.add(encodedProtectedHeaders);
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ vector<uint8_t> emptyExternalAad;
+ macStructure.add(emptyExternalAad);
+
+ // Next field is the payload, independently of how it's transported (RFC
+ // 8152 section 4.4). Since our API specifies only one of |data| and
+ // |detachedContent| can be non-empty, it's simply just the non-empty one.
+ if (data.size() > 0) {
+ macStructure.add(data);
+ } else {
+ macStructure.add(detachedContent);
+ }
+
+ return macStructure.encode();
+}
+
+optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint8_t>& data,
+ const vector<uint8_t>& detachedContent) {
+ cppbor::Map unprotectedHeaders;
+ cppbor::Map protectedHeaders;
+
+ if (data.size() > 0 && detachedContent.size() > 0) {
+ LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+ return {};
+ }
+
+ protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
+
+ vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);
+ vector<uint8_t> toBeMACed = coseBuildToBeMACed(encodedProtectedHeaders, data, detachedContent);
+
+ optional<vector<uint8_t>> mac = hmacSha256(key, toBeMACed);
+ if (!mac) {
+ LOG(ERROR) << "Error MACing toBeMACed data";
+ return {};
+ }
+
+ cppbor::Array array;
+ array.add(encodedProtectedHeaders);
+ array.add(std::move(unprotectedHeaders));
+ if (data.size() == 0) {
+ cppbor::Null nullValue;
+ array.add(std::move(nullValue));
+ } else {
+ array.add(data);
+ }
+ array.add(mac.value());
+ return array.encode();
+}
+
+// ---------------------------------------------------------------------------
+// Platform abstraction.
+// ---------------------------------------------------------------------------
+
+// This is not a very random HBK but that's OK because this is the SW
+// implementation where it can't be kept secret.
+vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+const vector<uint8_t>& getHardwareBoundKey() {
+ return hardwareBoundKey;
+}
+
+// ---------------------------------------------------------------------------
+// Utility functions specific to IdentityCredential.
+// ---------------------------------------------------------------------------
+
+Result okResult{ResultCode::OK, ""};
+
+const Result& resultOK() {
+ return okResult;
+}
+
+Result result(ResultCode code, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ string str;
+ android::base::StringAppendV(&str, format, ap);
+ va_end(ap);
+ return Result{code, str};
+}
+
+vector<vector<uint8_t>> chunkVector(const vector<uint8_t>& content, size_t maxChunkSize) {
+ vector<vector<uint8_t>> ret;
+
+ size_t contentSize = content.size();
+ if (contentSize <= maxChunkSize) {
+ ret.push_back(content);
+ return ret;
+ }
+
+ size_t numChunks = (contentSize + maxChunkSize - 1) / maxChunkSize;
+
+ size_t pos = 0;
+ for (size_t n = 0; n < numChunks; n++) {
+ size_t size = contentSize - pos;
+ if (size > maxChunkSize) {
+ size = maxChunkSize;
+ }
+ auto begin = content.begin() + pos;
+ auto end = content.begin() + pos + size;
+ ret.emplace_back(vector<uint8_t>(begin, end));
+ pos += maxChunkSize;
+ }
+
+ return ret;
+}
+
+vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
+ cppbor::Map map;
+ map.add("id", profile.id);
+
+ if (profile.readerCertificate.size() > 0) {
+ map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate));
+ }
+
+ if (profile.userAuthenticationRequired) {
+ map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
+ map.add("timeoutMillis", profile.timeoutMillis);
+ map.add("secureUserId", profile.secureUserId);
+ }
+
+ return map.encode();
+}
+
+optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
+ const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
+ vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
+
+ optional<vector<uint8_t>> nonce = getRandom(12);
+ if (!nonce) {
+ return {};
+ }
+ optional<vector<uint8_t>> macO = encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
+ if (!macO) {
+ return {};
+ }
+ return macO.value();
+}
+
+bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
+ const vector<uint8_t>& storageKey) {
+ vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
+
+ if (profile.mac.size() < kAesGcmIvSize) {
+ return false;
+ }
+ vector<uint8_t> nonce =
+ vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + kAesGcmIvSize);
+ optional<vector<uint8_t>> mac = encryptAes128Gcm(storageKey, nonce, {}, cborData);
+ if (!mac) {
+ return false;
+ }
+ if (mac.value() != vector<uint8_t>(profile.mac)) {
+ return false;
+ }
+ return true;
+}
+
+vector<uint8_t> testHardwareBoundKey = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+const vector<uint8_t>& getTestHardwareBoundKey() {
+ return testHardwareBoundKey;
+}
+
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+ const vector<uint16_t> accessControlProfileIds) {
+ cppbor::Map map;
+ map.add("Namespace", nameSpace);
+ map.add("Name", name);
+
+ cppbor::Array acpIds;
+ for (auto id : accessControlProfileIds) {
+ acpIds.add(id);
+ }
+ map.add("AccessControlProfileIds", std::move(acpIds));
+ return map.encode();
+}
+
+} // namespace support
+} // namespace identity
+} // namespace hardware
+} // namespace android
diff --git a/identity/support/src/cppbor.cpp b/identity/support/src/cppbor.cpp
new file mode 100644
index 0000000..d289985
--- /dev/null
+++ b/identity/support/src/cppbor.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2019, 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 "cppbor.h"
+#include "cppbor_parse.h"
+
+#define LOG_TAG "CppBor"
+#include <android-base/logging.h>
+
+namespace cppbor {
+
+namespace {
+
+template <typename T, typename Iterator, typename = std::enable_if<std::is_unsigned<T>::value>>
+Iterator writeBigEndian(T value, Iterator pos) {
+ for (unsigned i = 0; i < sizeof(value); ++i) {
+ *pos++ = static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1)));
+ value = static_cast<T>(value << 8);
+ }
+ return pos;
+}
+
+template <typename T, typename = std::enable_if<std::is_unsigned<T>::value>>
+void writeBigEndian(T value, std::function<void(uint8_t)>& cb) {
+ for (unsigned i = 0; i < sizeof(value); ++i) {
+ cb(static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1))));
+ value = static_cast<T>(value << 8);
+ }
+}
+
+} // namespace
+
+size_t headerSize(uint64_t addlInfo) {
+ if (addlInfo < ONE_BYTE_LENGTH) return 1;
+ if (addlInfo <= std::numeric_limits<uint8_t>::max()) return 2;
+ if (addlInfo <= std::numeric_limits<uint16_t>::max()) return 3;
+ if (addlInfo <= std::numeric_limits<uint32_t>::max()) return 5;
+ return 9;
+}
+
+uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end) {
+ size_t sz = headerSize(addlInfo);
+ if (end - pos < static_cast<ssize_t>(sz)) return nullptr;
+ switch (sz) {
+ case 1:
+ *pos++ = type | static_cast<uint8_t>(addlInfo);
+ return pos;
+ case 2:
+ *pos++ = type | ONE_BYTE_LENGTH;
+ *pos++ = static_cast<uint8_t>(addlInfo);
+ return pos;
+ case 3:
+ *pos++ = type | TWO_BYTE_LENGTH;
+ return writeBigEndian(static_cast<uint16_t>(addlInfo), pos);
+ case 5:
+ *pos++ = type | FOUR_BYTE_LENGTH;
+ return writeBigEndian(static_cast<uint32_t>(addlInfo), pos);
+ case 9:
+ *pos++ = type | EIGHT_BYTE_LENGTH;
+ return writeBigEndian(addlInfo, pos);
+ default:
+ CHECK(false); // Impossible to get here.
+ return nullptr;
+ }
+}
+
+void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback) {
+ size_t sz = headerSize(addlInfo);
+ switch (sz) {
+ case 1:
+ encodeCallback(type | static_cast<uint8_t>(addlInfo));
+ break;
+ case 2:
+ encodeCallback(type | ONE_BYTE_LENGTH);
+ encodeCallback(static_cast<uint8_t>(addlInfo));
+ break;
+ case 3:
+ encodeCallback(type | TWO_BYTE_LENGTH);
+ writeBigEndian(static_cast<uint16_t>(addlInfo), encodeCallback);
+ break;
+ case 5:
+ encodeCallback(type | FOUR_BYTE_LENGTH);
+ writeBigEndian(static_cast<uint32_t>(addlInfo), encodeCallback);
+ break;
+ case 9:
+ encodeCallback(type | EIGHT_BYTE_LENGTH);
+ writeBigEndian(addlInfo, encodeCallback);
+ break;
+ default:
+ CHECK(false); // Impossible to get here.
+ }
+}
+
+bool Item::operator==(const Item& other) const& {
+ if (type() != other.type()) return false;
+ switch (type()) {
+ case UINT:
+ return *asUint() == *(other.asUint());
+ case NINT:
+ return *asNint() == *(other.asNint());
+ case BSTR:
+ return *asBstr() == *(other.asBstr());
+ case TSTR:
+ return *asTstr() == *(other.asTstr());
+ case ARRAY:
+ return *asArray() == *(other.asArray());
+ case MAP:
+ return *asMap() == *(other.asMap());
+ case SIMPLE:
+ return *asSimple() == *(other.asSimple());
+ case SEMANTIC:
+ return *asSemantic() == *(other.asSemantic());
+ default:
+ CHECK(false); // Impossible to get here.
+ return false;
+ }
+}
+
+Nint::Nint(int64_t v) : mValue(v) {
+ CHECK(v < 0) << "Only negative values allowed";
+}
+
+bool Simple::operator==(const Simple& other) const& {
+ if (simpleType() != other.simpleType()) return false;
+
+ switch (simpleType()) {
+ case BOOLEAN:
+ return *asBool() == *(other.asBool());
+ case NULL_T:
+ return true;
+ default:
+ CHECK(false); // Impossible to get here.
+ return false;
+ }
+}
+
+uint8_t* Bstr::encode(uint8_t* pos, const uint8_t* end) const {
+ pos = encodeHeader(mValue.size(), pos, end);
+ if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr;
+ return std::copy(mValue.begin(), mValue.end(), pos);
+}
+
+void Bstr::encodeValue(EncodeCallback encodeCallback) const {
+ for (auto c : mValue) {
+ encodeCallback(c);
+ }
+}
+
+uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const {
+ pos = encodeHeader(mValue.size(), pos, end);
+ if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr;
+ return std::copy(mValue.begin(), mValue.end(), pos);
+}
+
+void Tstr::encodeValue(EncodeCallback encodeCallback) const {
+ for (auto c : mValue) {
+ encodeCallback(static_cast<uint8_t>(c));
+ }
+}
+
+bool CompoundItem::operator==(const CompoundItem& other) const& {
+ return type() == other.type() //
+ && addlInfo() == other.addlInfo() //
+ // Can't use vector::operator== because the contents are pointers. std::equal lets us
+ // provide a predicate that does the dereferencing.
+ && std::equal(mEntries.begin(), mEntries.end(), other.mEntries.begin(),
+ [](auto& a, auto& b) -> bool { return *a == *b; });
+}
+
+uint8_t* CompoundItem::encode(uint8_t* pos, const uint8_t* end) const {
+ pos = encodeHeader(addlInfo(), pos, end);
+ if (!pos) return nullptr;
+ for (auto& entry : mEntries) {
+ pos = entry->encode(pos, end);
+ if (!pos) return nullptr;
+ }
+ return pos;
+}
+
+void CompoundItem::encode(EncodeCallback encodeCallback) const {
+ encodeHeader(addlInfo(), encodeCallback);
+ for (auto& entry : mEntries) {
+ entry->encode(encodeCallback);
+ }
+}
+
+void Map::assertInvariant() const {
+ CHECK(mEntries.size() % 2 == 0);
+}
+
+std::unique_ptr<Item> Map::clone() const {
+ assertInvariant();
+ auto res = std::make_unique<Map>();
+ for (size_t i = 0; i < mEntries.size(); i += 2) {
+ res->add(mEntries[i]->clone(), mEntries[i + 1]->clone());
+ }
+ return res;
+}
+
+std::unique_ptr<Item> Array::clone() const {
+ auto res = std::make_unique<Array>();
+ for (size_t i = 0; i < mEntries.size(); i++) {
+ res->add(mEntries[i]->clone());
+ }
+ return res;
+}
+
+void Semantic::assertInvariant() const {
+ CHECK(mEntries.size() == 1);
+}
+
+} // namespace cppbor
diff --git a/identity/support/src/cppbor_parse.cpp b/identity/support/src/cppbor_parse.cpp
new file mode 100644
index 0000000..c9ebb8a
--- /dev/null
+++ b/identity/support/src/cppbor_parse.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2019, 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 "cppbor_parse.h"
+
+#include <sstream>
+#include <stack>
+
+#define LOG_TAG "CppBor"
+#include <android-base/logging.h>
+
+namespace cppbor {
+
+namespace {
+
+std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail,
+ const std::string& type) {
+ std::stringstream errStream;
+ errStream << "Need " << bytesNeeded << " byte(s) for " << type << ", have " << bytesAvail
+ << ".";
+ return errStream.str();
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
+std::tuple<bool, uint64_t, const uint8_t*> parseLength(const uint8_t* pos, const uint8_t* end,
+ ParseClient* parseClient) {
+ if (pos + sizeof(T) > end) {
+ parseClient->error(pos - 1, insufficientLengthString(sizeof(T), end - pos, "length field"));
+ return {false, 0, pos};
+ }
+
+ const uint8_t* intEnd = pos + sizeof(T);
+ T result = 0;
+ do {
+ result = static_cast<T>((result << 8) | *pos++);
+ } while (pos < intEnd);
+ return {true, result, pos};
+}
+
+std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end,
+ ParseClient* parseClient);
+
+std::tuple<const uint8_t*, ParseClient*> handleUint(uint64_t value, const uint8_t* hdrBegin,
+ const uint8_t* hdrEnd,
+ ParseClient* parseClient) {
+ std::unique_ptr<Item> item = std::make_unique<Uint>(value);
+ return {hdrEnd,
+ parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleNint(uint64_t value, const uint8_t* hdrBegin,
+ const uint8_t* hdrEnd,
+ ParseClient* parseClient) {
+ if (value > std::numeric_limits<int64_t>::max()) {
+ parseClient->error(hdrBegin, "NINT values that don't fit in int64_t are not supported.");
+ return {hdrBegin, nullptr /* end parsing */};
+ }
+ std::unique_ptr<Item> item = std::make_unique<Nint>(-1 - static_cast<uint64_t>(value));
+ return {hdrEnd,
+ parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleBool(uint64_t value, const uint8_t* hdrBegin,
+ const uint8_t* hdrEnd,
+ ParseClient* parseClient) {
+ std::unique_ptr<Item> item = std::make_unique<Bool>(value == TRUE);
+ return {hdrEnd,
+ parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleNull(const uint8_t* hdrBegin, const uint8_t* hdrEnd,
+ ParseClient* parseClient) {
+ std::unique_ptr<Item> item = std::make_unique<Null>();
+ return {hdrEnd,
+ parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+template <typename T>
+std::tuple<const uint8_t*, ParseClient*> handleString(uint64_t length, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end,
+ const std::string& errLabel,
+ ParseClient* parseClient) {
+ if (end - valueBegin < static_cast<ssize_t>(length)) {
+ parseClient->error(hdrBegin, insufficientLengthString(length, end - valueBegin, errLabel));
+ return {hdrBegin, nullptr /* end parsing */};
+ }
+
+ std::unique_ptr<Item> item = std::make_unique<T>(valueBegin, valueBegin + length);
+ return {valueBegin + length,
+ parseClient->item(item, hdrBegin, valueBegin, valueBegin + length)};
+}
+
+class IncompleteItem {
+ public:
+ virtual ~IncompleteItem() {}
+ virtual void add(std::unique_ptr<Item> item) = 0;
+};
+
+class IncompleteArray : public Array, public IncompleteItem {
+ public:
+ IncompleteArray(size_t size) : mSize(size) {}
+
+ // We return the "complete" size, rather than the actual size.
+ size_t size() const override { return mSize; }
+
+ void add(std::unique_ptr<Item> item) override {
+ mEntries.reserve(mSize);
+ mEntries.push_back(std::move(item));
+ }
+
+ private:
+ size_t mSize;
+};
+
+class IncompleteMap : public Map, public IncompleteItem {
+ public:
+ IncompleteMap(size_t size) : mSize(size) {}
+
+ // We return the "complete" size, rather than the actual size.
+ size_t size() const override { return mSize; }
+
+ void add(std::unique_ptr<Item> item) override {
+ mEntries.reserve(mSize * 2);
+ mEntries.push_back(std::move(item));
+ }
+
+ private:
+ size_t mSize;
+};
+
+class IncompleteSemantic : public Semantic, public IncompleteItem {
+ public:
+ IncompleteSemantic(uint64_t value) : Semantic(value) {}
+
+ // We return the "complete" size, rather than the actual size.
+ size_t size() const override { return 1; }
+
+ void add(std::unique_ptr<Item> item) override {
+ mEntries.reserve(1);
+ mEntries.push_back(std::move(item));
+ }
+};
+
+std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const uint8_t* hdrBegin,
+ const uint8_t* pos, const uint8_t* end,
+ const std::string& typeName,
+ ParseClient* parseClient) {
+ while (entryCount > 0) {
+ --entryCount;
+ if (pos == end) {
+ parseClient->error(hdrBegin, "Not enough entries for " + typeName + ".");
+ return {hdrBegin, nullptr /* end parsing */};
+ }
+ std::tie(pos, parseClient) = parseRecursively(pos, end, parseClient);
+ if (!parseClient) return {hdrBegin, nullptr};
+ }
+ return {pos, parseClient};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleCompound(
+ std::unique_ptr<Item> item, uint64_t entryCount, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName,
+ ParseClient* parseClient) {
+ parseClient =
+ parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */);
+ if (!parseClient) return {hdrBegin, nullptr};
+
+ const uint8_t* pos;
+ std::tie(pos, parseClient) =
+ handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, parseClient);
+ if (!parseClient) return {hdrBegin, nullptr};
+
+ return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end,
+ ParseClient* parseClient) {
+ const uint8_t* pos = begin;
+
+ MajorType type = static_cast<MajorType>(*pos & 0xE0);
+ uint8_t tagInt = *pos & 0x1F;
+ ++pos;
+
+ bool success = true;
+ uint64_t addlData;
+ if (tagInt < ONE_BYTE_LENGTH || tagInt > EIGHT_BYTE_LENGTH) {
+ addlData = tagInt;
+ } else {
+ switch (tagInt) {
+ case ONE_BYTE_LENGTH:
+ std::tie(success, addlData, pos) = parseLength<uint8_t>(pos, end, parseClient);
+ break;
+
+ case TWO_BYTE_LENGTH:
+ std::tie(success, addlData, pos) = parseLength<uint16_t>(pos, end, parseClient);
+ break;
+
+ case FOUR_BYTE_LENGTH:
+ std::tie(success, addlData, pos) = parseLength<uint32_t>(pos, end, parseClient);
+ break;
+
+ case EIGHT_BYTE_LENGTH:
+ std::tie(success, addlData, pos) = parseLength<uint64_t>(pos, end, parseClient);
+ break;
+
+ default:
+ CHECK(false); // It's impossible to get here
+ break;
+ }
+ }
+
+ if (!success) return {begin, nullptr};
+
+ switch (type) {
+ case UINT:
+ return handleUint(addlData, begin, pos, parseClient);
+
+ case NINT:
+ return handleNint(addlData, begin, pos, parseClient);
+
+ case BSTR:
+ return handleString<Bstr>(addlData, begin, pos, end, "byte string", parseClient);
+
+ case TSTR:
+ return handleString<Tstr>(addlData, begin, pos, end, "text string", parseClient);
+
+ case ARRAY:
+ return handleCompound(std::make_unique<IncompleteArray>(addlData), addlData, begin, pos,
+ end, "array", parseClient);
+
+ case MAP:
+ return handleCompound(std::make_unique<IncompleteMap>(addlData), addlData * 2, begin,
+ pos, end, "map", parseClient);
+
+ case SEMANTIC:
+ return handleCompound(std::make_unique<IncompleteSemantic>(addlData), 1, begin, pos,
+ end, "semantic", parseClient);
+
+ case SIMPLE:
+ switch (addlData) {
+ case TRUE:
+ case FALSE:
+ return handleBool(addlData, begin, pos, parseClient);
+ case NULL_V:
+ return handleNull(begin, pos, parseClient);
+ }
+ }
+ CHECK(false); // Impossible to get here.
+ return {};
+}
+
+class FullParseClient : public ParseClient {
+ public:
+ virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*,
+ const uint8_t* end) override {
+ if (mParentStack.empty() && !item->isCompound()) {
+ // This is the first and only item.
+ mTheItem = std::move(item);
+ mPosition = end;
+ return nullptr; // We're done.
+ }
+
+ if (item->isCompound()) {
+ // Starting a new compound data item, i.e. a new parent. Save it on the parent stack.
+ // It's safe to save a raw pointer because the unique_ptr is guaranteed to stay in
+ // existence until the corresponding itemEnd() call.
+ assert(dynamic_cast<CompoundItem*>(item.get()));
+ mParentStack.push(static_cast<CompoundItem*>(item.get()));
+ return this;
+ } else {
+ appendToLastParent(std::move(item));
+ return this;
+ }
+ }
+
+ virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*,
+ const uint8_t* end) override {
+ CHECK(item->isCompound() && item.get() == mParentStack.top());
+ mParentStack.pop();
+
+ if (mParentStack.empty()) {
+ mTheItem = std::move(item);
+ mPosition = end;
+ return nullptr; // We're done
+ } else {
+ appendToLastParent(std::move(item));
+ return this;
+ }
+ }
+
+ virtual void error(const uint8_t* position, const std::string& errorMessage) override {
+ mPosition = position;
+ mErrorMessage = errorMessage;
+ }
+
+ std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+ std::string /* errMsg */>
+ parseResult() {
+ std::unique_ptr<Item> p = std::move(mTheItem);
+ return {std::move(p), mPosition, std::move(mErrorMessage)};
+ }
+
+ private:
+ void appendToLastParent(std::unique_ptr<Item> item) {
+ auto parent = mParentStack.top();
+ assert(dynamic_cast<IncompleteItem*>(parent));
+ if (parent->type() == ARRAY) {
+ static_cast<IncompleteArray*>(parent)->add(std::move(item));
+ } else if (parent->type() == MAP) {
+ static_cast<IncompleteMap*>(parent)->add(std::move(item));
+ } else if (parent->type() == SEMANTIC) {
+ static_cast<IncompleteSemantic*>(parent)->add(std::move(item));
+ } else {
+ CHECK(false); // Impossible to get here.
+ }
+ }
+
+ std::unique_ptr<Item> mTheItem;
+ std::stack<CompoundItem*> mParentStack;
+ const uint8_t* mPosition = nullptr;
+ std::string mErrorMessage;
+};
+
+} // anonymous namespace
+
+void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) {
+ parseRecursively(begin, end, parseClient);
+}
+
+std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+ std::string /* errMsg */>
+parse(const uint8_t* begin, const uint8_t* end) {
+ FullParseClient parseClient;
+ parse(begin, end, &parseClient);
+ return parseClient.parseResult();
+}
+
+} // namespace cppbor
diff --git a/identity/support/tests/IdentityCredentialSupportTest.cpp b/identity/support/tests/IdentityCredentialSupportTest.cpp
new file mode 100644
index 0000000..c356549
--- /dev/null
+++ b/identity/support/tests/IdentityCredentialSupportTest.cpp
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2019, 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 <iomanip>
+#include <iostream>
+#include <sstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+using std::optional;
+using std::string;
+using std::vector;
+
+namespace android {
+namespace hardware {
+namespace identity {
+
+TEST(IdentityCredentialSupport, encodeHex) {
+ EXPECT_EQ("", support::encodeHex(vector<uint8_t>({})));
+ EXPECT_EQ("01", support::encodeHex(vector<uint8_t>({1})));
+ EXPECT_EQ("000102030405060708090a0b0c0d0e0f10",
+ support::encodeHex(
+ vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})));
+ EXPECT_EQ("0102ffe060", support::encodeHex(vector<uint8_t>({1, 2, 255, 224, 96})));
+}
+
+TEST(IdentityCredentialSupport, decodeHex) {
+ EXPECT_EQ(vector<uint8_t>({}), support::decodeHex(""));
+ EXPECT_EQ(vector<uint8_t>({1}), support::decodeHex("01"));
+
+ EXPECT_EQ(vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
+ support::decodeHex("000102030405060708090a0b0c0d0e0f10"));
+
+ EXPECT_FALSE(support::decodeHex("0g"));
+ EXPECT_FALSE(support::decodeHex("0"));
+ EXPECT_FALSE(support::decodeHex("012"));
+}
+
+TEST(IdentityCredentialSupport, CborPrettyPrint) {
+ EXPECT_EQ("'Some text'", support::cborPrettyPrint(cppbor::Tstr("Some text").encode()));
+
+ EXPECT_EQ("''", support::cborPrettyPrint(cppbor::Tstr("").encode()));
+
+ EXPECT_EQ("{0x01, 0x00, 0x02, 0xf0, 0xff, 0x40}",
+ support::cborPrettyPrint(
+ cppbor::Bstr(vector<uint8_t>({1, 0, 2, 240, 255, 64})).encode()));
+
+ EXPECT_EQ("{}", support::cborPrettyPrint(cppbor::Bstr(vector<uint8_t>()).encode()));
+
+ EXPECT_EQ("true", support::cborPrettyPrint(cppbor::Bool(true).encode()));
+
+ EXPECT_EQ("false", support::cborPrettyPrint(cppbor::Bool(false).encode()));
+
+ EXPECT_EQ("42", support::cborPrettyPrint(cppbor::Uint(42).encode()));
+
+ EXPECT_EQ("9223372036854775807", // 0x7fff ffff ffff ffff
+ support::cborPrettyPrint(cppbor::Uint(std::numeric_limits<int64_t>::max()).encode()));
+
+ EXPECT_EQ("-42", support::cborPrettyPrint(cppbor::Nint(-42).encode()));
+
+ EXPECT_EQ("-9223372036854775808", // -0x8000 0000 0000 0000
+ support::cborPrettyPrint(cppbor::Nint(std::numeric_limits<int64_t>::min()).encode()));
+}
+
+TEST(IdentityCredentialSupport, CborPrettyPrintCompound) {
+ cppbor::Array array = cppbor::Array("foo", "bar", "baz");
+ EXPECT_EQ("['foo', 'bar', 'baz', ]", support::cborPrettyPrint(array.encode()));
+
+ cppbor::Map map = cppbor::Map().add("foo", 42).add("bar", 43).add("baz", 44);
+ EXPECT_EQ(
+ "{\n"
+ " 'foo' : 42,\n"
+ " 'bar' : 43,\n"
+ " 'baz' : 44,\n"
+ "}",
+ support::cborPrettyPrint(map.encode()));
+
+ cppbor::Array array2 = cppbor::Array(cppbor::Tstr("Some text"), cppbor::Nint(-42));
+ EXPECT_EQ("['Some text', -42, ]", support::cborPrettyPrint(array2.encode()));
+
+ cppbor::Map map2 = cppbor::Map().add(42, "foo").add(43, "bar").add(44, "baz");
+ EXPECT_EQ(
+ "{\n"
+ " 42 : 'foo',\n"
+ " 43 : 'bar',\n"
+ " 44 : 'baz',\n"
+ "}",
+ support::cborPrettyPrint(map2.encode()));
+
+ cppbor::Array deeplyNestedArrays =
+ cppbor::Array(cppbor::Array(cppbor::Array("a", "b", "c")),
+ cppbor::Array(cppbor::Array("d", "e", cppbor::Array("f", "g"))));
+ EXPECT_EQ(
+ "[\n"
+ " ['a', 'b', 'c', ],\n"
+ " [\n 'd',\n"
+ " 'e',\n"
+ " ['f', 'g', ],\n"
+ " ],\n"
+ "]",
+ support::cborPrettyPrint(deeplyNestedArrays.encode()));
+
+ EXPECT_EQ(
+ "[\n"
+ " {0x0a, 0x0b},\n"
+ " 'foo',\n"
+ " 42,\n"
+ " ['foo', 'bar', 'baz', ],\n"
+ " {\n"
+ " 'foo' : 42,\n"
+ " 'bar' : 43,\n"
+ " 'baz' : 44,\n"
+ " },\n"
+ " {\n"
+ " 'deep1' : ['Some text', -42, ],\n"
+ " 'deep2' : {\n"
+ " 42 : 'foo',\n"
+ " 43 : 'bar',\n"
+ " 44 : 'baz',\n"
+ " },\n"
+ " },\n"
+ "]",
+ support::cborPrettyPrint(cppbor::Array(cppbor::Bstr(vector<uint8_t>{10, 11}),
+ cppbor::Tstr("foo"), cppbor::Uint(42),
+ std::move(array), std::move(map),
+ (cppbor::Map()
+ .add("deep1", std::move(array2))
+ .add("deep2", std::move(map2))))
+ .encode()));
+}
+
+TEST(IdentityCredentialSupport, Signatures) {
+ vector<uint8_t> data = {1, 2, 3};
+
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ optional<vector<uint8_t>> signature = support::signEcDsa(privKey.value(), data);
+ ASSERT_TRUE(
+ support::checkEcDsaSignature(support::sha256(data), signature.value(), pubKey.value()));
+
+ // Manipulate the signature, check that verification fails.
+ vector<uint8_t> modifiedSignature = signature.value();
+ modifiedSignature[0] ^= 0xff;
+ ASSERT_FALSE(
+ support::checkEcDsaSignature(support::sha256(data), modifiedSignature, pubKey.value()));
+
+ // Manipulate the data being checked, check that verification fails.
+ vector<uint8_t> modifiedDigest = support::sha256(data);
+ modifiedDigest[0] ^= 0xff;
+ ASSERT_FALSE(support::checkEcDsaSignature(modifiedDigest, signature.value(), pubKey.value()));
+}
+
+string replaceLine(const string& str, ssize_t lineNumber, const string& replacement) {
+ vector<string> lines;
+ std::istringstream f(str);
+ string s;
+ while (std::getline(f, s, '\n')) {
+ lines.push_back(s);
+ }
+
+ size_t numLines = lines.size();
+ if (lineNumber < 0) {
+ lineNumber = numLines - (-lineNumber);
+ }
+
+ string ret;
+ size_t n = 0;
+ for (const string& line : lines) {
+ if (n == lineNumber) {
+ ret += replacement + "\n";
+ } else {
+ ret += line + "\n";
+ }
+ n++;
+ }
+ return ret;
+}
+
+TEST(IdentityCredentialSupport, CoseSignatures) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ vector<uint8_t> data = {1, 2, 3};
+ optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa(
+ privKey.value(), data, {} /* detachedContent */, {} /* x5chain */);
+ ASSERT_TRUE(support::coseCheckEcDsaSignature(coseSign1.value(), {} /* detachedContent */,
+ pubKey.value()));
+
+ optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+ ASSERT_TRUE(payload);
+ ASSERT_EQ(data, payload.value());
+
+ // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message
+ string out = support::cborPrettyPrint(coseSign1.value());
+ out = replaceLine(out, -2, " [] // Signature Removed");
+ EXPECT_EQ(
+ "[\n"
+ " {0xa1, 0x01, 0x26},\n" // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256"
+ " {},\n"
+ " {0x01, 0x02, 0x03},\n"
+ " [] // Signature Removed\n"
+ "]\n",
+ out);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesAdditionalData) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ vector<uint8_t> detachedContent = {1, 2, 3};
+ optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa(privKey.value(), {} /* data */,
+ detachedContent, {} /* x5chain */);
+ ASSERT_TRUE(
+ support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+ optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+ ASSERT_TRUE(payload);
+ ASSERT_EQ(0, payload.value().size());
+
+ // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message
+ string out = support::cborPrettyPrint(coseSign1.value());
+ out = replaceLine(out, -2, " [] // Signature Removed");
+ EXPECT_EQ(
+ "[\n"
+ " {0xa1, 0x01, 0x26},\n" // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256"
+ " {},\n"
+ " null,\n"
+ " [] // Signature Removed\n"
+ "]\n",
+ out);
+}
+
+vector<uint8_t> generateCertChain(size_t numCerts) {
+ vector<vector<uint8_t>> certs;
+
+ for (size_t n = 0; n < numCerts; n++) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+
+ optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+ pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+ certs.push_back(cert.value());
+ }
+ return support::certificateChainJoin(certs);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithSingleCert) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ vector<uint8_t> certChain = generateCertChain(1);
+ optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain);
+ ASSERT_EQ(1, splitCerts.value().size());
+
+ vector<uint8_t> detachedContent = {1, 2, 3};
+ optional<vector<uint8_t>> coseSign1 =
+ support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain);
+ ASSERT_TRUE(
+ support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+ optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+ ASSERT_TRUE(payload);
+ ASSERT_EQ(0, payload.value().size());
+
+ optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value());
+ EXPECT_EQ(certsRecovered.value(), certChain);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithMultipleCerts) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ vector<uint8_t> certChain = generateCertChain(5);
+ optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain);
+ ASSERT_EQ(5, splitCerts.value().size());
+
+ vector<uint8_t> detachedContent = {1, 2, 3};
+ optional<vector<uint8_t>> coseSign1 =
+ support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain);
+ ASSERT_TRUE(
+ support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+ optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+ ASSERT_TRUE(payload);
+ ASSERT_EQ(0, payload.value().size());
+
+ optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value());
+ EXPECT_EQ(certsRecovered.value(), certChain);
+}
+
+TEST(IdentityCredentialSupport, CertificateChain) {
+ optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+ ASSERT_TRUE(keyPair);
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(privKey);
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(pubKey);
+
+ optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+ pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+
+ optional<vector<uint8_t>> extractedPubKey =
+ support::certificateChainGetTopMostKey(cert.value());
+ ASSERT_TRUE(extractedPubKey);
+ ASSERT_EQ(pubKey.value(), extractedPubKey.value());
+
+ // We expect to the chain returned by ecPublicKeyGenerateCertificate() to only have a
+ // single element
+ optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(cert.value());
+ ASSERT_EQ(1, splitCerts.value().size());
+ ASSERT_EQ(splitCerts.value()[0], cert.value());
+
+ optional<vector<uint8_t>> otherKeyPair = support::createEcKeyPair();
+ ASSERT_TRUE(otherKeyPair);
+ optional<vector<uint8_t>> otherPrivKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+ ASSERT_TRUE(otherPrivKey);
+ optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+ ASSERT_TRUE(otherPubKey);
+ optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate(
+ otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+
+ // Now both cert and otherCert are two distinct certificates. Let's make a
+ // chain and check that certificateChainSplit() works as expected.
+ ASSERT_NE(cert.value(), otherCert.value());
+ const vector<vector<uint8_t>> certs2 = {cert.value(), otherCert.value()};
+ vector<uint8_t> certs2combined = support::certificateChainJoin(certs2);
+ ASSERT_EQ(certs2combined.size(), cert.value().size() + otherCert.value().size());
+ optional<vector<vector<uint8_t>>> splitCerts2 = support::certificateChainSplit(certs2combined);
+ ASSERT_EQ(certs2, splitCerts2.value());
+}
+
+vector<uint8_t> strToVec(const string& str) {
+ vector<uint8_t> ret;
+ size_t size = str.size();
+ ret.resize(size);
+ memcpy(ret.data(), str.data(), size);
+ return ret;
+}
+
+// Test vector from https://en.wikipedia.org/wiki/HMAC
+TEST(IdentityCredentialSupport, hmacSha256) {
+ vector<uint8_t> key = strToVec("key");
+ vector<uint8_t> data = strToVec("The quick brown fox jumps over the lazy dog");
+
+ vector<uint8_t> expected =
+ support::decodeHex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8")
+ .value();
+
+ optional<vector<uint8_t>> hmac = support::hmacSha256(key, data);
+ ASSERT_TRUE(hmac);
+ ASSERT_EQ(expected, hmac.value());
+}
+
+// See also CoseMac0 test in UtilUnitTest.java inside cts/tests/tests/identity/
+TEST(IdentityCredentialSupport, CoseMac0) {
+ vector<uint8_t> key;
+ key.resize(32);
+ vector<uint8_t> data = {0x10, 0x11, 0x12, 0x13};
+ vector<uint8_t> detachedContent = {};
+
+ optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent);
+ ASSERT_TRUE(mac);
+
+ EXPECT_EQ(
+ "[\n"
+ " {0xa1, 0x01, 0x05},\n"
+ " {},\n"
+ " {0x10, 0x11, 0x12, 0x13},\n"
+ " {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, "
+ "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, "
+ "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n"
+ "]",
+ support::cborPrettyPrint(mac.value()));
+}
+
+TEST(IdentityCredentialSupport, CoseMac0DetachedContent) {
+ vector<uint8_t> key;
+ key.resize(32);
+ vector<uint8_t> data = {};
+ vector<uint8_t> detachedContent = {0x10, 0x11, 0x12, 0x13};
+
+ optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent);
+ ASSERT_TRUE(mac);
+
+ // Same HMAC as in CoseMac0 test, only difference is that payload is null.
+ EXPECT_EQ(
+ "[\n"
+ " {0xa1, 0x01, 0x05},\n"
+ " {},\n"
+ " null,\n"
+ " {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, "
+ "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, "
+ "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n"
+ "]",
+ support::cborPrettyPrint(mac.value()));
+}
+
+} // namespace identity
+} // namespace hardware
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/identity/support/tests/cppbor_test.cpp b/identity/support/tests/cppbor_test.cpp
new file mode 100644
index 0000000..3eb5598
--- /dev/null
+++ b/identity/support/tests/cppbor_test.cpp
@@ -0,0 +1,1013 @@
+/*
+ * Copyright (c) 2019, 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 <iomanip>
+#include <sstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "cppbor.h"
+#include "cppbor_parse.h"
+
+using namespace cppbor;
+using namespace std;
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::ByRef;
+using ::testing::InSequence;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::Truly;
+using ::testing::Unused;
+
+string hexDump(const string& str) {
+ stringstream s;
+ for (auto c : str) {
+ s << setfill('0') << setw(2) << hex << (static_cast<unsigned>(c) & 0xff);
+ }
+ return s.str();
+}
+
+TEST(SimpleValueTest, UnsignedValueSizes) {
+ // Check that unsigned integers encode to correct lengths, and that encodedSize() is correct.
+ vector<pair<uint64_t /* value */, size_t /* expected encoded size */>> testCases{
+ {0, 1},
+ {1, 1},
+ {23, 1},
+ {24, 2},
+ {255, 2},
+ {256, 3},
+ {65535, 3},
+ {65536, 5},
+ {4294967295, 5},
+ {4294967296, 9},
+ {std::numeric_limits<uint64_t>::max(), 9},
+ };
+ for (auto& testCase : testCases) {
+ Uint val(testCase.first);
+ EXPECT_EQ(testCase.second, val.encodedSize()) << "Wrong size for value " << testCase.first;
+ EXPECT_EQ(val.encodedSize(), val.toString().size())
+ << "encodedSize and encoding disagree for value " << testCase.first;
+ }
+}
+
+TEST(SimpleValueTest, UnsignedValueEncodings) {
+ EXPECT_EQ("\x00"s, Uint(0u).toString());
+ EXPECT_EQ("\x01"s, Uint(1u).toString());
+ EXPECT_EQ("\x0a"s, Uint(10u).toString());
+ EXPECT_EQ("\x17"s, Uint(23u).toString());
+ EXPECT_EQ("\x18\x18"s, Uint(24u).toString());
+ EXPECT_EQ("\x18\x19"s, Uint(25u).toString());
+ EXPECT_EQ("\x18\x64"s, Uint(100u).toString());
+ EXPECT_EQ("\x19\x03\xe8"s, Uint(1000u).toString());
+ EXPECT_EQ("\x1a\x00\x0f\x42\x40"s, Uint(1000000u).toString());
+ EXPECT_EQ("\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00"s, Uint(1000000000000u).toString());
+ EXPECT_EQ("\x1B\x7f\xff\xff\xff\xff\xff\xff\xff"s,
+ Uint(std::numeric_limits<int64_t>::max()).toString());
+}
+
+TEST(SimpleValueTest, NegativeValueEncodings) {
+ EXPECT_EQ("\x20"s, Nint(-1).toString());
+ EXPECT_EQ("\x28"s, Nint(-9).toString());
+ EXPECT_EQ("\x29"s, Nint(-10).toString());
+ EXPECT_EQ("\x36"s, Nint(-23).toString());
+ EXPECT_EQ("\x37"s, Nint(-24).toString());
+ EXPECT_EQ("\x38\x18"s, Nint(-25).toString());
+ EXPECT_EQ("\x38\x62"s, Nint(-99).toString());
+ EXPECT_EQ("\x38\x63"s, Nint(-100).toString());
+ EXPECT_EQ("\x39\x03\xe6"s, Nint(-999).toString());
+ EXPECT_EQ("\x39\x03\xe7"s, Nint(-1000).toString());
+ EXPECT_EQ("\x3a\x00\x0f\x42\x3F"s, Nint(-1000000).toString());
+ EXPECT_EQ("\x3b\x00\x00\x00\xe8\xd4\xa5\x0f\xff"s, Nint(-1000000000000).toString());
+ EXPECT_EQ("\x3B\x7f\xff\xff\xff\xff\xff\xff\xff"s,
+ Nint(std::numeric_limits<int64_t>::min()).toString());
+}
+
+TEST(SimpleValueDeathTest, NegativeValueEncodings) {
+ EXPECT_DEATH(Nint(0), "");
+ EXPECT_DEATH(Nint(1), "");
+}
+
+TEST(SimpleValueTest, BooleanEncodings) {
+ EXPECT_EQ("\xf4"s, Bool(false).toString());
+ EXPECT_EQ("\xf5"s, Bool(true).toString());
+}
+
+TEST(SimpleValueTest, ByteStringEncodings) {
+ EXPECT_EQ("\x40", Bstr("").toString());
+ EXPECT_EQ("\x41\x61", Bstr("a").toString());
+ EXPECT_EQ("\x41\x41", Bstr("A").toString());
+ EXPECT_EQ("\x44\x49\x45\x54\x46", Bstr("IETF").toString());
+ EXPECT_EQ("\x42\x22\x5c", Bstr("\"\\").toString());
+ EXPECT_EQ("\x42\xc3\xbc", Bstr("\xc3\xbc").toString());
+ EXPECT_EQ("\x43\xe6\xb0\xb4", Bstr("\xe6\xb0\xb4").toString());
+ EXPECT_EQ("\x44\xf0\x90\x85\x91", Bstr("\xf0\x90\x85\x91").toString());
+ EXPECT_EQ("\x44\x01\x02\x03\x04", Bstr("\x01\x02\x03\x04").toString());
+ EXPECT_EQ("\x44\x40\x40\x40\x40", Bstr("@@@@").toString());
+}
+
+TEST(SimpleValueTest, TextStringEncodings) {
+ EXPECT_EQ("\x60"s, Tstr("").toString());
+ EXPECT_EQ("\x61\x61"s, Tstr("a").toString());
+ EXPECT_EQ("\x61\x41"s, Tstr("A").toString());
+ EXPECT_EQ("\x64\x49\x45\x54\x46"s, Tstr("IETF").toString());
+ EXPECT_EQ("\x62\x22\x5c"s, Tstr("\"\\").toString());
+ EXPECT_EQ("\x62\xc3\xbc"s, Tstr("\xc3\xbc").toString());
+ EXPECT_EQ("\x63\xe6\xb0\xb4"s, Tstr("\xe6\xb0\xb4").toString());
+ EXPECT_EQ("\x64\xf0\x90\x85\x91"s, Tstr("\xf0\x90\x85\x91").toString());
+ EXPECT_EQ("\x64\x01\x02\x03\x04"s, Tstr("\x01\x02\x03\x04").toString());
+}
+
+TEST(IsIteratorPairOverTest, All) {
+ EXPECT_TRUE((
+ details::is_iterator_pair_over<pair<string::iterator, string::iterator>, char>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<string::const_iterator, string::iterator>,
+ char>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<string::iterator, string::const_iterator>,
+ char>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, char*>, char>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<const char*, char*>, char>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, const char*>, char>::value));
+ EXPECT_FALSE((details::is_iterator_pair_over<pair<string::iterator, string::iterator>,
+ uint8_t>::value));
+ EXPECT_FALSE((details::is_iterator_pair_over<pair<char*, char*>, uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<
+ pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<
+ pair<vector<uint8_t>::const_iterator, vector<uint8_t>::iterator>,
+ uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<
+ pair<vector<uint8_t>::iterator, vector<uint8_t>::const_iterator>,
+ uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, uint8_t*>, uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<const uint8_t*, uint8_t*>, uint8_t>::value));
+ EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, uint8_t>::value));
+ EXPECT_FALSE((details::is_iterator_pair_over<
+ pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, char>::value));
+ EXPECT_FALSE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, char>::value));
+}
+
+TEST(MakeEntryTest, Boolean) {
+ EXPECT_EQ("\xf4"s, details::makeItem(false)->toString());
+}
+
+TEST(MakeEntryTest, Integers) {
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint8_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint16_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint32_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint64_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<int8_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<int16_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<int32_t>(0))->toString());
+ EXPECT_EQ("\x00"s, details::makeItem(static_cast<int64_t>(0))->toString());
+ EXPECT_EQ("\x20"s, details::makeItem(static_cast<int8_t>(-1))->toString());
+ EXPECT_EQ("\x20"s, details::makeItem(static_cast<int16_t>(-1))->toString());
+ EXPECT_EQ("\x20"s, details::makeItem(static_cast<int32_t>(-1))->toString());
+ EXPECT_EQ("\x20"s, details::makeItem(static_cast<int64_t>(-1))->toString());
+
+ EXPECT_EQ("\x1b\xff\xff\xff\xff\xff\xff\xff\xff"s,
+ details::makeItem(static_cast<uint64_t>(std::numeric_limits<uint64_t>::max()))
+ ->toString());
+}
+
+TEST(MakeEntryTest, StdStrings) {
+ string s1("hello");
+ const string s2("hello");
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); // copy of string
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s,
+ details::makeItem(s2)->toString()); // copy of const string
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s,
+ details::makeItem(std::move(s1))->toString()); // move string
+ EXPECT_EQ(0U, s1.size()); // Prove string was moved, not copied.
+}
+
+TEST(MakeEntryTest, StdStringViews) {
+ string_view s1("hello");
+ const string_view s2("hello");
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString());
+}
+
+TEST(MakeEntryTest, CStrings) {
+ char s1[] = "hello";
+ const char s2[] = "hello";
+ const char* s3 = "hello";
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s3)->toString());
+}
+
+TEST(MakeEntryTest, StringIteratorPairs) {
+ // Use iterators from string to prove that "real" iterators work
+ string s1 = "hello"s;
+ pair<string::iterator, string::iterator> p1 = make_pair(s1.begin(), s1.end());
+
+ const pair<string::iterator, string::iterator> p2 = p1;
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p1)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p2)->toString());
+
+ // Use char*s as iterators
+ const char* s2 = "hello";
+ pair p3 = make_pair(s2, s2 + 5);
+ const pair p4 = p3;
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p3)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p4)->toString());
+}
+
+TEST(MakeEntryTest, ByteStrings) {
+ vector<uint8_t> v1 = {0x00, 0x01, 0x02};
+ const vector<uint8_t> v2 = {0x00, 0x01, 0x02};
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v1)->toString()); // copy of vector
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v2)->toString()); // copy of const vector
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(std::move(v1))->toString()); // move vector
+ EXPECT_EQ(0U, v1.size()); // Prove vector was moved, not copied.
+}
+
+TEST(MakeEntryTest, ByteStringIteratorPairs) {
+ using vec = vector<uint8_t>;
+ using iter = vec::iterator;
+ vec v1 = {0x00, 0x01, 0x02};
+ pair<iter, iter> p1 = make_pair(v1.begin(), v1.end());
+ const pair<iter, iter> p2 = make_pair(v1.begin(), v1.end());
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p1)->toString());
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p2)->toString());
+
+ // Use uint8_t*s as iterators
+ uint8_t v2[] = {0x00, 0x01, 0x02};
+ uint8_t* v3 = v2;
+ pair<uint8_t*, uint8_t*> p3 = make_pair(v2, v2 + 3);
+ const pair<uint8_t*, uint8_t*> p4 = make_pair(v2, v2 + 3);
+ pair<uint8_t*, uint8_t*> p5 = make_pair(v3, v3 + 3);
+ const pair<uint8_t*, uint8_t*> p6 = make_pair(v3, v3 + 3);
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p3)->toString());
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p4)->toString());
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p5)->toString());
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p6)->toString());
+}
+
+TEST(MakeEntryTest, ByteStringBuffers) {
+ uint8_t v1[] = {0x00, 0x01, 0x02};
+ EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(make_pair(v1, 3))->toString());
+}
+
+TEST(MakeEntryTest, ItemPointer) {
+ Uint* p1 = new Uint(0);
+ EXPECT_EQ("\x00"s, details::makeItem(p1)->toString());
+ EXPECT_EQ("\x60"s, details::makeItem(new Tstr(string()))->toString());
+}
+
+TEST(MakeEntryTest, ItemReference) {
+ Tstr str("hello"s);
+ Tstr& strRef = str;
+ const Tstr& strConstRef = str;
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(str)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strRef)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strConstRef)->toString());
+ EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(std::move(str))->toString());
+ EXPECT_EQ("\x60"s, details::makeItem(str)->toString()); // Prove that it moved
+
+ EXPECT_EQ("\x00"s, details::makeItem(Uint(0))->toString());
+
+ EXPECT_EQ("\x43\x00\x01\x02"s,
+ details::makeItem(Bstr(vector<uint8_t>{0x00, 0x01, 0x02}))->toString());
+
+ EXPECT_EQ("\x80"s, details::makeItem(Array())->toString());
+ EXPECT_EQ("\xa0"s, details::makeItem(Map())->toString());
+}
+
+TEST(CompoundValueTest, ArrayOfInts) {
+ EXPECT_EQ("\x80"s, Array().toString());
+ Array(Uint(0)).toString();
+
+ EXPECT_EQ("\x81\x00"s, Array(Uint(0U)).toString());
+ EXPECT_EQ("\x82\x00\x01"s, Array(Uint(0), Uint(1)).toString());
+ EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(Uint(0), Uint(1), Nint(-99)).toString());
+
+ EXPECT_EQ("\x81\x00"s, Array(0).toString());
+ EXPECT_EQ("\x82\x00\x01"s, Array(0, 1).toString());
+ EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(0, 1, -99).toString());
+}
+
+TEST(CompoundValueTest, MapOfInts) {
+ EXPECT_EQ("\xA0"s, Map().toString());
+ EXPECT_EQ("\xA1\x00\x01"s, Map(Uint(0), Uint(1)).toString());
+ // Maps with an odd number of arguments will fail to compile. Uncomment the next lines to test.
+ // EXPECT_EQ("\xA1\x00"s, Map(Int(0)).toString());
+ // EXPECT_EQ("\xA1\x00\x01\x02"s, Map(Int(0), Int(1), Int(2)).toString());
+}
+
+TEST(CompoundValueTest, MixedArray) {
+ vector<uint8_t> vec = {3, 2, 1};
+ EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+ Array(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString());
+
+ EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+ Array(1, -1, vec, "hello").toString());
+}
+
+TEST(CompoundValueTest, MixedMap) {
+ vector<uint8_t> vec = {3, 2, 1};
+ EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+ Map(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString());
+
+ EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+ Map(1, -1, vec, "hello").toString());
+}
+
+TEST(CompoundValueTest, NestedStructures) {
+ vector<uint8_t> vec = {3, 2, 1};
+
+ string expectedEncoding =
+ "\xA2\x66\x4F\x75\x74\x65\x72\x31\x82\xA2\x66\x49\x6E\x6E\x65\x72\x31\x18\x63\x66\x49"
+ "\x6E"
+ "\x6E\x65\x72\x32\x43\x03\x02\x01\x63\x66\x6F\x6F\x66\x4F\x75\x74\x65\x72\x32\x0A"s;
+
+ // Do it with explicitly-created Items
+ EXPECT_EQ(expectedEncoding,
+ Map(Tstr("Outer1"),
+ Array( //
+ Map(Tstr("Inner1"), Uint(99), Tstr("Inner2"), Bstr(vec)), Tstr("foo")),
+ Tstr("Outer2"), //
+ Uint(10))
+ .toString());
+ EXPECT_EQ(3U, vec.size());
+
+ // Now just use convertible types
+ EXPECT_EQ(expectedEncoding, Map("Outer1",
+ Array(Map("Inner1", 99, //
+ "Inner2", vec),
+ "foo"),
+ "Outer2", 10)
+ .toString());
+ EXPECT_EQ(3U, vec.size());
+
+ // Finally, do it with the .add() method. This is slightly less efficient, but has the
+ // advantage you can build a structure up incrementally, or somewhat fluently if you like.
+ // First, fluently.
+ EXPECT_EQ(expectedEncoding, Map().add("Outer1", Array().add(Map() //
+ .add("Inner1", 99)
+ .add("Inner2", vec))
+ .add("foo"))
+ .add("Outer2", 10)
+ .toString());
+ EXPECT_EQ(3U, vec.size());
+
+ // Next, more incrementally
+ Array arr;
+ arr.add(Map() //
+ .add("Inner1", 99)
+ .add("Inner2", vec))
+ .add("foo");
+ EXPECT_EQ(3U, vec.size());
+
+ Map m;
+ m.add("Outer1", std::move(arr)); // Moving is necessary; Map and Array cannot be copied.
+ m.add("Outer2", 10);
+ auto s = m.toString();
+ EXPECT_EQ(expectedEncoding, s);
+}
+
+TEST(EncodingMethodsTest, AllVariants) {
+ Map map;
+ map.add("key1", Array().add(Map() //
+ .add("key_a", 9999999)
+ .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03})
+ .add("key_c", std::numeric_limits<uint64_t>::max())
+ .add("key_d", std::numeric_limits<int16_t>::min()))
+ .add("foo"))
+ .add("key2", true);
+
+ std::vector<uint8_t> buf;
+ buf.resize(map.encodedSize());
+
+ EXPECT_EQ(buf.data() + buf.size(), map.encode(buf.data(), buf.data() + buf.size()));
+
+ EXPECT_EQ(buf, map.encode());
+
+ std::vector<uint8_t> buf2;
+ map.encode(std::back_inserter(buf2));
+ EXPECT_EQ(buf, buf2);
+
+ auto iter = buf.begin();
+ map.encode([&](uint8_t c) { EXPECT_EQ(c, *iter++); });
+}
+
+TEST(EncodingMethodsTest, UintWithTooShortBuf) {
+ Uint val(100000);
+ vector<uint8_t> buf(val.encodedSize() - 1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, TstrWithTooShortBuf) {
+ Tstr val("01234567890123456789012345"s);
+ vector<uint8_t> buf(1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+
+ buf.resize(val.encodedSize() - 1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, BstrWithTooShortBuf) {
+ Bstr val("01234567890123456789012345"s);
+ vector<uint8_t> buf(1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+
+ buf.resize(val.encodedSize() - 1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, ArrayWithTooShortBuf) {
+ Array val("a", 5, -100);
+
+ std::vector<uint8_t> buf(val.encodedSize() - 1);
+ EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, MapWithTooShortBuf) {
+ Map map;
+ map.add("key1", Array().add(Map() //
+ .add("key_a", 99)
+ .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03}))
+ .add("foo"))
+ .add("key2", true);
+
+ std::vector<uint8_t> buf(map.encodedSize() - 1);
+ EXPECT_EQ(nullptr, map.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EqualityTest, Uint) {
+ Uint val(99);
+ EXPECT_EQ(val, Uint(99));
+
+ EXPECT_NE(val, Uint(98));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("99"));
+ EXPECT_NE(val, Bool(false));
+ EXPECT_NE(val, Array(99, 1));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Nint) {
+ Nint val(-1);
+ EXPECT_EQ(val, Nint(-1));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("99"));
+ EXPECT_NE(val, Bool(false));
+ EXPECT_NE(val, Array(99));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Tstr) {
+ Tstr val("99");
+ EXPECT_EQ(val, Tstr("99"));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("98"));
+ EXPECT_NE(val, Bstr("99"));
+ EXPECT_NE(val, Bool(false));
+ EXPECT_NE(val, Array(99, 1));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Bstr) {
+ Bstr val("99");
+ EXPECT_EQ(val, Bstr("99"));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("98"));
+ EXPECT_NE(val, Bool(false));
+ EXPECT_NE(val, Array(99, 1));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Bool) {
+ Bool val(false);
+ EXPECT_EQ(val, Bool(false));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("98"));
+ EXPECT_NE(val, Bool(true));
+ EXPECT_NE(val, Array(99, 1));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Array) {
+ Array val(99, 1);
+ EXPECT_EQ(val, Array(99, 1));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("98"));
+ EXPECT_NE(val, Bool(true));
+ EXPECT_NE(val, Array(99, 2));
+ EXPECT_NE(val, Array(98, 1));
+ EXPECT_NE(val, Array(99, 1, 2));
+ EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Map) {
+ Map val(99, 1);
+ EXPECT_EQ(val, Map(99, 1));
+
+ EXPECT_NE(val, Uint(99));
+ EXPECT_NE(val, Nint(-1));
+ EXPECT_NE(val, Nint(-4));
+ EXPECT_NE(val, Tstr("99"));
+ EXPECT_NE(val, Bstr("98"));
+ EXPECT_NE(val, Bool(true));
+ EXPECT_NE(val, Array(99, 1));
+ EXPECT_NE(val, Map(99, 2));
+ EXPECT_NE(val, Map(99, 1, 99, 2));
+}
+
+TEST(ConvertTest, Uint) {
+ unique_ptr<Item> item = details::makeItem(10);
+
+ EXPECT_EQ(UINT, item->type());
+ EXPECT_NE(nullptr, item->asInt());
+ EXPECT_NE(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ(10, item->asInt()->value());
+ EXPECT_EQ(10, item->asUint()->value());
+}
+
+TEST(ConvertTest, Nint) {
+ unique_ptr<Item> item = details::makeItem(-10);
+
+ EXPECT_EQ(NINT, item->type());
+ EXPECT_NE(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_NE(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ(-10, item->asInt()->value());
+ EXPECT_EQ(-10, item->asNint()->value());
+}
+
+TEST(ConvertTest, Tstr) {
+ unique_ptr<Item> item = details::makeItem("hello");
+
+ EXPECT_EQ(TSTR, item->type());
+ EXPECT_EQ(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_NE(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ("hello"s, item->asTstr()->value());
+}
+
+TEST(ConvertTest, Bstr) {
+ vector<uint8_t> vec{0x23, 0x24, 0x22};
+ unique_ptr<Item> item = details::makeItem(vec);
+
+ EXPECT_EQ(BSTR, item->type());
+ EXPECT_EQ(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_NE(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ(vec, item->asBstr()->value());
+}
+
+TEST(ConvertTest, Bool) {
+ unique_ptr<Item> item = details::makeItem(false);
+
+ EXPECT_EQ(SIMPLE, item->type());
+ EXPECT_EQ(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_NE(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ(BOOLEAN, item->asSimple()->simpleType());
+ EXPECT_NE(nullptr, item->asSimple()->asBool());
+
+ EXPECT_FALSE(item->asSimple()->asBool()->value());
+}
+
+TEST(ConvertTest, Map) {
+ unique_ptr<Item> item(new Map);
+
+ EXPECT_EQ(MAP, item->type());
+ EXPECT_EQ(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_NE(nullptr, item->asMap());
+ EXPECT_EQ(nullptr, item->asArray());
+
+ EXPECT_EQ(0U, item->asMap()->size());
+}
+
+TEST(ConvertTest, Array) {
+ unique_ptr<Item> item(new Array);
+
+ EXPECT_EQ(ARRAY, item->type());
+ EXPECT_EQ(nullptr, item->asInt());
+ EXPECT_EQ(nullptr, item->asUint());
+ EXPECT_EQ(nullptr, item->asNint());
+ EXPECT_EQ(nullptr, item->asTstr());
+ EXPECT_EQ(nullptr, item->asBstr());
+ EXPECT_EQ(nullptr, item->asSimple());
+ EXPECT_EQ(nullptr, item->asMap());
+ EXPECT_NE(nullptr, item->asArray());
+
+ EXPECT_EQ(0U, item->asArray()->size());
+}
+
+class MockParseClient : public ParseClient {
+ public:
+ MOCK_METHOD4(item, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end));
+ MOCK_METHOD4(itemEnd, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+ const uint8_t* valueBegin, const uint8_t* end));
+ MOCK_METHOD2(error, void(const uint8_t* position, const std::string& errorMessage));
+};
+
+MATCHER_P(IsType, value, std::string("Type ") + (negation ? "doesn't match" : "matches")) {
+ return arg->type() == value;
+}
+
+MATCHER_P(MatchesItem, value, "") {
+ return arg && *arg == value;
+}
+
+MATCHER_P(IsArrayOfSize, value, "") {
+ return arg->type() == ARRAY && arg->asArray()->size() == value;
+}
+
+MATCHER_P(IsMapOfSize, value, "") {
+ return arg->type() == MAP && arg->asMap()->size() == value;
+}
+
+TEST(StreamParseTest, Uint) {
+ MockParseClient mpc;
+
+ Uint val(100);
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Nint) {
+ MockParseClient mpc;
+
+ Nint val(-10);
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Bool) {
+ MockParseClient mpc;
+
+ Bool val(true);
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Tstr) {
+ MockParseClient mpc;
+
+ Tstr val("Hello");
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Bstr) {
+ MockParseClient mpc;
+
+ Bstr val("Hello");
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Array) {
+ MockParseClient mpc;
+
+ Array val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max());
+ ASSERT_NE(val[2]->asArray(), nullptr);
+ const Array& interior = *(val[2]->asArray());
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ {
+ InSequence s;
+ const uint8_t* pos = encBegin;
+ EXPECT_CALL(mpc, item(IsArrayOfSize(val.size()), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0])), pos, pos + 1, pos + 6))
+ .WillOnce(Return(&mpc));
+ pos += 6;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1])), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ const uint8_t* innerArrayBegin = pos;
+ EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8))
+ .WillOnce(Return(&mpc));
+ pos += 8;
+ EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin,
+ innerArrayBegin + 1, pos))
+ .WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[3])), pos, pos + 9, pos + 9))
+ .WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(val.size()), encBegin, encBegin + 1, encEnd))
+ .WillOnce(Return(&mpc));
+ }
+
+ EXPECT_CALL(mpc, error(_, _)) //
+ .Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Map) {
+ MockParseClient mpc;
+
+ Map val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max());
+ ASSERT_NE(val[1].first->asArray(), nullptr);
+ const Array& interior = *(val[1].first->asArray());
+ auto encoded = val.encode();
+ uint8_t* encBegin = encoded.data();
+ uint8_t* encEnd = encoded.data() + encoded.size();
+
+ {
+ InSequence s;
+ const uint8_t* pos = encBegin;
+ EXPECT_CALL(mpc, item(_, pos, pos + 1, pos + 1)).WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].first)), pos, pos + 1, pos + 6))
+ .WillOnce(Return(&mpc));
+ pos += 6;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].second)), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ const uint8_t* innerArrayBegin = pos;
+ EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1))
+ .WillOnce(Return(&mpc));
+ ++pos;
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8))
+ .WillOnce(Return(&mpc));
+ pos += 8;
+ EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin,
+ innerArrayBegin + 1, pos))
+ .WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1].second)), pos, pos + 9, pos + 9))
+ .WillOnce(Return(&mpc));
+ EXPECT_CALL(mpc, itemEnd(IsMapOfSize(val.size()), encBegin, encBegin + 1, encEnd))
+ .WillOnce(Return(&mpc));
+ }
+
+ EXPECT_CALL(mpc, error(_, _)) //
+ .Times(0);
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Semantic) {
+ MockParseClient mpc;
+
+ vector<uint8_t> encoded;
+ auto iter = back_inserter(encoded);
+ encodeHeader(SEMANTIC, 0, iter);
+ Uint(999).encode(iter);
+
+ EXPECT_CALL(mpc, item(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+ EXPECT_CALL(mpc, error(encoded.data(), "Semantic tags not supported"));
+
+ parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(FullParserTest, Uint) {
+ Uint val(10);
+
+ auto [item, pos, message] = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Nint) {
+ Nint val(-10);
+
+ auto [item, pos, message] = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(val));
+
+ vector<uint8_t> minNint = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+ std::tie(item, pos, message) = parse(minNint);
+ EXPECT_THAT(item, NotNull());
+ EXPECT_EQ(item->asNint()->value(), std::numeric_limits<int64_t>::min());
+}
+
+TEST(FullParserTest, NintOutOfRange) {
+ vector<uint8_t> outOfRangeNint = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+ auto [item, pos, message] = parse(outOfRangeNint);
+ EXPECT_THAT(item, IsNull());
+ EXPECT_EQ(pos, outOfRangeNint.data());
+ EXPECT_EQ(message, "NINT values that don't fit in int64_t are not supported.");
+}
+
+TEST(FullParserTest, Tstr) {
+ Tstr val("Hello");
+
+ auto [item, pos, message] = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Bstr) {
+ Bstr val("\x00\x01\0x02"s);
+
+ auto [item, pos, message] = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Array) {
+ Array val("hello", -4, 3);
+
+ auto encoded = val.encode();
+ auto [item, pos, message] = parse(encoded);
+ EXPECT_THAT(item, MatchesItem(ByRef(val)));
+ EXPECT_EQ(pos, encoded.data() + encoded.size());
+ EXPECT_EQ("", message);
+
+ // We've already checked it all, but walk it just for fun.
+ ASSERT_NE(nullptr, item->asArray());
+ const Array& arr = *(item->asArray());
+ ASSERT_EQ(arr[0]->type(), TSTR);
+ EXPECT_EQ(arr[0]->asTstr()->value(), "hello");
+}
+
+TEST(FullParserTest, Map) {
+ Map val("hello", -4, 3, Bstr("hi"));
+
+ auto [item, pos, message] = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(ByRef(val)));
+}
+
+TEST(FullParserTest, Complex) {
+ vector<uint8_t> vec = {0x01, 0x02, 0x08, 0x03};
+ Map val("Outer1",
+ Array(Map("Inner1", 99, //
+ "Inner2", vec),
+ "foo"),
+ "Outer2", 10);
+
+ std::unique_ptr<Item> item;
+ const uint8_t* pos;
+ std::string message;
+ std::tie(item, pos, message) = parse(val.encode());
+ EXPECT_THAT(item, MatchesItem(ByRef(val)));
+}
+
+TEST(FullParserTest, IncompleteUint) {
+ Uint val(1000);
+
+ auto encoding = val.encode();
+ auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+ EXPECT_EQ(nullptr, item.get());
+ EXPECT_EQ(encoding.data(), pos);
+ EXPECT_EQ("Need 2 byte(s) for length field, have 1.", message);
+}
+
+TEST(FullParserTest, IncompleteString) {
+ Tstr val("hello");
+
+ auto encoding = val.encode();
+ auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2);
+ EXPECT_EQ(nullptr, item.get());
+ EXPECT_EQ(encoding.data(), pos);
+ EXPECT_EQ("Need 5 byte(s) for text string, have 3.", message);
+}
+
+TEST(FullParserTest, ArrayWithInsufficientEntries) {
+ Array val(1, 2, 3, 4);
+
+ auto encoding = val.encode();
+ auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+ EXPECT_EQ(nullptr, item.get());
+ EXPECT_EQ(encoding.data(), pos);
+ EXPECT_EQ("Not enough entries for array.", message);
+}
+
+TEST(FullParserTest, ArrayWithTruncatedEntry) {
+ Array val(1, 2, 3, 400000);
+
+ auto encoding = val.encode();
+ auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+ EXPECT_EQ(nullptr, item.get());
+ EXPECT_EQ(encoding.data() + encoding.size() - 5, pos);
+ EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message);
+}
+
+TEST(FullParserTest, MapWithTruncatedEntry) {
+ Map val(1, 2, 300000, 4);
+
+ auto encoding = val.encode();
+ auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2);
+ EXPECT_EQ(nullptr, item.get());
+ EXPECT_EQ(encoding.data() + 3, pos);
+ EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message);
+}
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/neuralnetworks/1.3/types.hal b/neuralnetworks/1.3/types.hal
index 74c259c..62c5833 100644
--- a/neuralnetworks/1.3/types.hal
+++ b/neuralnetworks/1.3/types.hal
@@ -4747,7 +4747,7 @@
RESIZE_NEAREST_NEIGHBOR = @1.2::OperationType:RESIZE_NEAREST_NEIGHBOR,
/**
- * Quantized version of {@link OperationType:LSTM}.
+ * Quantized version of {@link OperationType::LSTM}.
*
* The input and the output use asymmetric quantized types, while the rest
* use symmetric ones.
diff --git a/neuralnetworks/1.3/types.t b/neuralnetworks/1.3/types.t
index e06f5d6..0d20d06 100644
--- a/neuralnetworks/1.3/types.t
+++ b/neuralnetworks/1.3/types.t
@@ -57,6 +57,8 @@
%insert Operation_1.2
+%insert Operation_1.3
+
/**
* DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
* OEM operation and data types.
diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
index e3c5376..eced063 100644
--- a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
+++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
@@ -500,7 +500,7 @@
class DynamicOutputShapeTest : public GeneratedTest {};
// Tag for the dynamic output shape tests
-class DISABLED_QuantizationCouplingTest : public GeneratedTest {};
+class QuantizationCouplingTest : public GeneratedTest {};
TEST_P(GeneratedTest, Test) {
Execute(kDevice, kTestModel, /*testKind=*/TestKind::GENERAL);
@@ -510,7 +510,7 @@
Execute(kDevice, kTestModel, /*testKind=*/TestKind::DYNAMIC_SHAPE);
}
-TEST_P(DISABLED_QuantizationCouplingTest, Test) {
+TEST_P(QuantizationCouplingTest, Test) {
Execute(kDevice, kTestModel, /*testKind=*/TestKind::QUANTIZATION_COUPLING);
}
@@ -520,7 +520,7 @@
INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest,
[](const TestModel& testModel) { return !testModel.expectFailure; });
-INSTANTIATE_GENERATED_TEST(DISABLED_QuantizationCouplingTest, [](const TestModel& testModel) {
+INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
return testModel.hasQuant8CoupledOperands() && testModel.operations.size() == 1;
});