Merge "Add Identity Credential HAL, default implementation, and VTS tests."

am: ba60e1fa4a

Change-Id: Ic404209ca7e0b6439f7050e794fe637402c64571
This commit is contained in:
David Zeuthen 2020-01-15 15:22:01 -08:00 committed by android-build-merger
commit c1e8c37554
29 changed files with 8601 additions and 0 deletions

View file

@ -615,6 +615,10 @@ dd377f404a8e71f6191d295e10067db629b0f0c28e594af906f2bea5d87fe2cc android.hardwar
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

25
identity/1.0/Android.bp Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,2 @@
swillden@google.com
zeuthen@google.com

View file

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

View file

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

View file

@ -0,0 +1,3 @@
service vendor.identity-1-0 /vendor/bin/hw/android.hardware.identity@1.0-service.example
class hal
user nobody

View file

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

164
identity/1.0/types.hal Normal file
View file

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

2
identity/1.0/vts/OWNERS Normal file
View file

@ -0,0 +1,2 @@
swillden@google.com
zeuthen@google.com

View file

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

View file

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

103
identity/support/Android.bp Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff