Merge "Add Identity Credential HAL, default implementation, and VTS tests."
am: ba60e1fa4a
Change-Id: Ic404209ca7e0b6439f7050e794fe637402c64571
This commit is contained in:
commit
c1e8c37554
29 changed files with 8601 additions and 0 deletions
|
@ -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
25
identity/1.0/Android.bp
Normal 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,
|
||||
}
|
343
identity/1.0/IIdentityCredential.hal
Normal file
343
identity/1.0/IIdentityCredential.hal
Normal 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);
|
||||
};
|
185
identity/1.0/IIdentityCredentialStore.hal
Normal file
185
identity/1.0/IIdentityCredentialStore.hal
Normal 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);
|
||||
};
|
236
identity/1.0/IWritableIdentityCredential.hal
Normal file
236
identity/1.0/IWritableIdentityCredential.hal
Normal 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);
|
||||
};
|
43
identity/1.0/default/Android.bp
Normal file
43
identity/1.0/default/Android.bp
Normal 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",
|
||||
],
|
||||
}
|
773
identity/1.0/default/IdentityCredential.cpp
Normal file
773
identity/1.0/default/IdentityCredential.cpp
Normal 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
|
132
identity/1.0/default/IdentityCredential.h
Normal file
132
identity/1.0/default/IdentityCredential.h
Normal 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
|
68
identity/1.0/default/IdentityCredentialStore.cpp
Normal file
68
identity/1.0/default/IdentityCredentialStore.cpp
Normal 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
|
55
identity/1.0/default/IdentityCredentialStore.h
Normal file
55
identity/1.0/default/IdentityCredentialStore.h
Normal 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
|
2
identity/1.0/default/OWNERS
Normal file
2
identity/1.0/default/OWNERS
Normal file
|
@ -0,0 +1,2 @@
|
|||
swillden@google.com
|
||||
zeuthen@google.com
|
426
identity/1.0/default/WritableIdentityCredential.cpp
Normal file
426
identity/1.0/default/WritableIdentityCredential.cpp
Normal 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
|
105
identity/1.0/default/WritableIdentityCredential.h
Normal file
105
identity/1.0/default/WritableIdentityCredential.h
Normal 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
|
|
@ -0,0 +1,3 @@
|
|||
service vendor.identity-1-0 /vendor/bin/hw/android.hardware.identity@1.0-service.example
|
||||
class hal
|
||||
user nobody
|
40
identity/1.0/default/service.cpp
Normal file
40
identity/1.0/default/service.cpp
Normal 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
164
identity/1.0/types.hal
Normal 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
2
identity/1.0/vts/OWNERS
Normal file
|
@ -0,0 +1,2 @@
|
|||
swillden@google.com
|
||||
zeuthen@google.com
|
36
identity/1.0/vts/functional/Android.bp
Normal file
36
identity/1.0/vts/functional/Android.bp
Normal 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",
|
||||
],
|
||||
}
|
|
@ -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
103
identity/support/Android.bp
Normal 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"],
|
||||
}
|
|
@ -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_
|
216
identity/support/include/cppbor/README.md
Normal file
216
identity/support/include/cppbor/README.md
Normal 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.
|
827
identity/support/include/cppbor/cppbor.h
Normal file
827
identity/support/include/cppbor/cppbor.h
Normal 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
|
133
identity/support/include/cppbor/cppbor_parse.h
Normal file
133
identity/support/include/cppbor/cppbor_parse.h
Normal 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
|
1815
identity/support/src/IdentityCredentialSupport.cpp
Normal file
1815
identity/support/src/IdentityCredentialSupport.cpp
Normal file
File diff suppressed because it is too large
Load diff
225
identity/support/src/cppbor.cpp
Normal file
225
identity/support/src/cppbor.cpp
Normal 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
|
351
identity/support/src/cppbor_parse.cpp
Normal file
351
identity/support/src/cppbor_parse.cpp
Normal 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
|
446
identity/support/tests/IdentityCredentialSupportTest.cpp
Normal file
446
identity/support/tests/IdentityCredentialSupportTest.cpp
Normal 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();
|
||||
}
|
1013
identity/support/tests/cppbor_test.cpp
Normal file
1013
identity/support/tests/cppbor_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue