diff options
Diffstat (limited to 'identity/aidl/default/libeic')
-rw-r--r-- | identity/aidl/default/libeic/EicCbor.c | 236 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicCbor.h | 156 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicOps.h | 299 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicPresentation.c | 728 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicPresentation.h | 229 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicProvisioning.c | 290 | ||||
-rw-r--r-- | identity/aidl/default/libeic/EicProvisioning.h | 123 | ||||
-rw-r--r-- | identity/aidl/default/libeic/libeic.h | 39 |
8 files changed, 2100 insertions, 0 deletions
diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c new file mode 100644 index 0000000000..ec049b1c0d --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.c @@ -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. + */ + +#include "EicCbor.h" + +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256; + eicOpsSha256Init(&cbor->digester.sha256); +} + +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256; + eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize); +} + +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Final(&cbor->digester.sha256, digest); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest); + break; + } +} + +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Update(&cbor->digester.sha256, data, size); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size); + break; + } + + if (cbor->size >= cbor->bufferSize) { + cbor->size += size; + return; + } + + size_t numBytesLeft = cbor->bufferSize - cbor->size; + size_t numBytesToCopy = size; + if (numBytesToCopy > numBytesLeft) { + numBytesToCopy = numBytesLeft; + } + eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy); + + cbor->size += size; +} + +size_t eicCborAdditionalLengthBytesFor(size_t size) { + if (size < 24) { + return 0; + } else if (size <= 0xff) { + return 1; + } else if (size <= 0xffff) { + return 2; + } else if (size <= 0xffffffff) { + return 4; + } + return 8; +} + +void eicCborBegin(EicCbor* cbor, int majorType, size_t size) { + uint8_t data[9]; + + if (size < 24) { + data[0] = (majorType << 5) | size; + eicCborAppend(cbor, data, 1); + } else if (size <= 0xff) { + data[0] = (majorType << 5) | 24; + data[1] = size; + eicCborAppend(cbor, data, 2); + } else if (size <= 0xffff) { + data[0] = (majorType << 5) | 25; + data[1] = size >> 8; + data[2] = size & 0xff; + eicCborAppend(cbor, data, 3); + } else if (size <= 0xffffffff) { + data[0] = (majorType << 5) | 26; + data[1] = (size >> 24) & 0xff; + data[2] = (size >> 16) & 0xff; + data[3] = (size >> 8) & 0xff; + data[4] = size & 0xff; + eicCborAppend(cbor, data, 5); + } else { + data[0] = (majorType << 5) | 24; + data[1] = (((uint64_t)size) >> 56) & 0xff; + data[2] = (((uint64_t)size) >> 48) & 0xff; + data[3] = (((uint64_t)size) >> 40) & 0xff; + data[4] = (((uint64_t)size) >> 32) & 0xff; + data[5] = (((uint64_t)size) >> 24) & 0xff; + data[6] = (((uint64_t)size) >> 16) & 0xff; + data[7] = (((uint64_t)size) >> 8) & 0xff; + data[8] = ((uint64_t)size) & 0xff; + eicCborAppend(cbor, data, 9); + } +} + +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize); + eicCborAppend(cbor, data, dataSize); +} + +void eicCborAppendString(EicCbor* cbor, const char* str) { + size_t length = eicStrLen(str); + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length); + eicCborAppend(cbor, (const uint8_t*)str, length); +} + +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue); +} + +void eicCborAppendBool(EicCbor* cbor, bool value) { + uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE; + eicCborAppendSimple(cbor, simpleValue); +} + +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded); +} + +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded); +} + +void eicCborAppendNumber(EicCbor* cbor, int64_t value) { + if (value < 0) { + size_t encoded = -1 - value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded); + } else { + eicCborAppendUnsigned(cbor, value); + } +} + +void eicCborAppendArray(EicCbor* cbor, size_t numElements) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements); +} + +void eicCborAppendMap(EicCbor* cbor, size_t numPairs) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs); +} + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) { + size_t numPairs = 1; + if (readerCertificateSize > 0) { + numPairs += 1; + } + if (userAuthenticationRequired) { + numPairs += 2; + if (secureUserId > 0) { + numPairs += 1; + } + } + eicCborAppendMap(cborBuilder, numPairs); + eicCborAppendString(cborBuilder, "id"); + eicCborAppendUnsigned(cborBuilder, id); + if (readerCertificateSize > 0) { + eicCborAppendString(cborBuilder, "readerCertificate"); + eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize); + } + if (userAuthenticationRequired) { + eicCborAppendString(cborBuilder, "userAuthenticationRequired"); + eicCborAppendBool(cborBuilder, userAuthenticationRequired); + eicCborAppendString(cborBuilder, "timeoutMillis"); + eicCborAppendUnsigned(cborBuilder, timeoutMillis); + if (secureUserId > 0) { + eicCborAppendString(cborBuilder, "secureUserId"); + eicCborAppendUnsigned(cborBuilder, secureUserId); + } + } + + if (cborBuilder->size > cborBuilder->bufferSize) { + eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize, + cborBuilder->size); + return false; + } + + return true; +} + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) { + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, cborBufferSize); + eicCborAppendMap(&cborBuilder, 3); + eicCborAppendString(&cborBuilder, "Namespace"); + eicCborAppendString(&cborBuilder, nameSpace); + eicCborAppendString(&cborBuilder, "Name"); + eicCborAppendString(&cborBuilder, name); + eicCborAppendString(&cborBuilder, "AccessControlProfileIds"); + eicCborAppendArray(&cborBuilder, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]); + } + if (cborBuilder.size > cborBufferSize) { + eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd", + cborBufferSize, cborBuilder.size); + return false; + } + if (outAdditionalDataCborSize != NULL) { + *outAdditionalDataCborSize = cborBuilder.size; + } + eicCborFinal(&cborBuilder, additionalDataSha256); + return true; +} diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h new file mode 100644 index 0000000000..4686b38447 --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.h @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H +#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicOps.h" + +typedef enum { + EIC_CBOR_DIGEST_TYPE_SHA256, + EIC_CBOR_DIGEST_TYPE_HMAC_SHA256, +} EicCborDigestType; + +/* EicCbor is a utility class to build CBOR data structures and calculate + * digests on the fly. + */ +typedef struct { + // Contains the size of the built CBOR, even if it exceeds bufferSize (will + // never write to buffer beyond bufferSize though) + size_t size; + + // The size of the buffer. Is zero if no data is recorded in which case + // only digesting is performed. + size_t bufferSize; + + // Whether we're producing a SHA-256 or HMAC-SHA256 digest. + EicCborDigestType digestType; + + // The SHA-256 digester object. + union { + EicSha256Ctx sha256; + EicHmacSha256Ctx hmacSha256; + } digester; + + // The buffer used for building up CBOR or NULL if bufferSize is 0. + uint8_t* buffer; +} EicCbor; + +/* Initializes an EicCbor. + * + * The given buffer will be used, up to bufferSize. + * + * If bufferSize is 0, buffer may be NULL. + */ +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize); + +/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256. + */ +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize); + +/* Finishes building CBOR and returns the digest. */ +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +/* Appends CBOR data to the EicCbor. */ +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size); + +#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0 +#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1 +#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2 +#define EIC_CBOR_MAJOR_TYPE_STRING 3 +#define EIC_CBOR_MAJOR_TYPE_ARRAY 4 +#define EIC_CBOR_MAJOR_TYPE_MAP 5 +#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6 +#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7 + +#define EIC_CBOR_SIMPLE_VALUE_FALSE 20 +#define EIC_CBOR_SIMPLE_VALUE_TRUE 21 + +#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24 + +/* Begins a new CBOR value. */ +void eicCborBegin(EicCbor* cbor, int majorType, size_t size); + +/* Appends a bytestring. */ +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize); + +/* Appends a NUL-terminated UTF-8 string. */ +void eicCborAppendString(EicCbor* cbor, const char* str); + +/* Appends a simple value. */ +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue); + +/* Appends a boolean. */ +void eicCborAppendBool(EicCbor* cbor, bool value); + +/* Appends a semantic */ +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value); + +/* Appends an unsigned number. */ +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value); + +/* Appends a number. */ +void eicCborAppendNumber(EicCbor* cbor, int64_t value); + +/* Starts appending an array. + * + * After this numElements CBOR elements must follow. + */ +void eicCborAppendArray(EicCbor* cbor, size_t numElements); + +/* Starts appending a map. + * + * After this numPairs pairs of CBOR elements must follow. + */ +void eicCborAppendMap(EicCbor* cbor, size_t numPairs); + +/* Calculates how many bytes are needed to store a size. */ +size_t eicCborAdditionalLengthBytesFor(size_t size); + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId); + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]); + +// The maximum size of an encoded Secure Access Control Profile that we +// support. Since the SACP may contain a reader certificate chain these can get +// pretty big. +// +// Currently we allocate space on the stack for this structure which is why we +// have a maximum size. We can get rid of the maximum size by incrementally +// building/verifying the SACP. TODO: actually do this. +// +#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512 + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h new file mode 100644 index 0000000000..da4dabf879 --- /dev/null +++ b/identity/aidl/default/libeic/EicOps.h @@ -0,0 +1,299 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H +#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H + +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> + +// Uncomment or define if debug messages are needed. +// +//#define EIC_DEBUG + +#ifdef __cplusplus +extern "C" { +#endif + +// The following defines must be set to something appropriate +// +// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx +// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx +// +// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined +// as sizeof(SHA256_CTX). +// +// We expect the implementation to provide a header file with the name +// EicOpsImpl.h to do all this. +// +#include "EicOpsImpl.h" + +#define EIC_SHA256_DIGEST_SIZE 32 + +// The size of a P-256 private key. +// +#define EIC_P256_PRIV_KEY_SIZE 32 + +// The size of a P-256 public key in uncompressed form. +// +// The public key is stored in uncompressed form, first the X coordinate, then +// the Y coordinate. +// +#define EIC_P256_PUB_KEY_SIZE 64 + +// Size of one of the coordinates in a curve-point. +// +#define EIC_P256_COORDINATE_SIZE 32 + +// The size of an ECSDA signature using P-256. +// +// The R and S values are stored here, first R then S. +// +#define EIC_ECDSA_P256_SIGNATURE_SIZE 64 + +#define EIC_AES_128_KEY_SIZE 16 + +// The following are definitions of implementation functions the +// underlying platform must provide. +// + +struct EicSha256Ctx { + uint8_t reserved[EIC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicSha256Ctx EicSha256Ctx; + +struct EicHmacSha256Ctx { + uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicHmacSha256Ctx EicHmacSha256Ctx; + +#ifdef EIC_DEBUG +// Debug macro. Don't include a new-line in message. +// +#define eicDebug(...) \ + do { \ + eicPrint("%s:%d: ", __FILE__, __LINE__); \ + eicPrint(__VA_ARGS__); \ + eicPrint("\n"); \ + } while (0) +#else +#define eicDebug(...) \ + do { \ + } while (0) +#endif + +// Prints message which should include new-line character. Can be no-op. +// +// Don't use this from code, use eicDebug() instead. +// +#ifdef EIC_DEBUG +void eicPrint(const char* format, ...); +#else +inline void eicPrint(const char*, ...) {} +#endif + +// Dumps data as pretty-printed hex. Can be no-op. +// +#ifdef EIC_DEBUG +void eicHexdump(const char* message, const uint8_t* data, size_t dataSize); +#else +inline void eicHexdump(const char*, const uint8_t*, size_t) {} +#endif + +// Pretty-prints encoded CBOR. Can be no-op. +// +// 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. +// +#ifdef EIC_DEBUG +void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize); +#else +inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {} +#endif + +// Memory setting, see memset(3). +void* eicMemSet(void* s, int c, size_t n); + +// Memory copying, see memcpy(3). +void* eicMemCpy(void* dest, const void* src, size_t n); + +// String length, see strlen(3). +size_t eicStrLen(const char* s); + +// Memory compare, see CRYPTO_memcmp(3SSL) +// +// It takes an amount of time dependent on len, but independent of the contents of the +// memory regions pointed to by s1 and s2. +// +int eicCryptoMemCmp(const void* s1, const void* s2, size_t n); + +// Random number generation. +bool eicOpsRandom(uint8_t* buf, size_t numBytes); + +// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes). +// +// Otherwise returns all zeroes (16 bytes). +// +const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential); + +// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|, +// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which +// must be of size |dataSize| + 28. +bool eicOpsEncryptAes128Gcm( + const uint8_t* key, // Must be 16 bytes + const uint8_t* nonce, // Must be 12 bytes + const uint8_t* data, // May be NULL if size is 0 + size_t dataSize, + const uint8_t* additionalAuthenticationData, // May be NULL if size is 0 + size_t additionalAuthenticationDataSize, uint8_t* encryptedData); + +// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|, +// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28. +// +// The format of |encryptedData| must be as specified in the +// encryptAes128Gcm() function. +bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes + const uint8_t* encryptedData, size_t encryptedDataSize, + const uint8_t* additionalAuthenticationData, + size_t additionalAuthenticationDataSize, uint8_t* data); + +// Creates an EC key using the P-256 curve. The private key is written to +// |privateKey|. The public key is written to |publicKey|. +// +bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]); + +// Generates CredentialKey plus an attestation certificate. +// +// The attestation certificate will be signed by the attestation keys the secure +// area has been provisioned with. The given |challenge| and |applicationId| +// will be used as will |testCredential|. +// +// The generated certificate will be in X.509 format and returned in |cert| +// and |certSize| must be set to the size of this array and this function will +// set it to the size of the certification chain on successfully return. +// +// This may return either a single certificate or an entire certificate +// chain. If it returns only a single certificate, the implementation of +// SecureHardwareProvisioningProxy::createCredentialKey() should amend the +// remainder of the certificate chain on the HAL side. +// +bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, bool testCredential, uint8_t* cert, + size_t* certSize); // inout + +// Generate an X.509 certificate for the key identified by |publicKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +// The certificate will be signed by the key identified by |signingKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial, + const char* issuerName, const char* subjectName, time_t validityNotBefore, + time_t validityNotAfter, uint8_t* cert, + size_t* certSize); // inout + +// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must +// be given by |digestOfData|). Returns the signature in |signature|. +// +bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE], + uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// Performs Elliptic Curve Diffie-Helman. +// +bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]); + +// Performs HKDF. +// +bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt, + size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output, + size_t outputSize); + +// SHA-256 functions. +void eicOpsSha256Init(EicSha256Ctx* ctx); +void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// HMAC SHA-256 functions. +void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize); +void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// Extracts the public key in the given X.509 certificate. +// +// If the key is not an EC key, this function fails. +// +// Otherwise the public key is stored in uncompressed form in |publicKey| which +// size should be set in |publicKeySize|. On successful return |publicKeySize| +// is set to the length of the key. If there is not enough space, the function +// fails. +// +// (The public key returned is not necessarily a P-256 key, even if it is note +// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.) +// +bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey, + size_t* publicKeySize); + +// Checks that the X.509 certificate given by |x509Cert| is signed by the public +// key given by |publicKey| which must be an EC key in uncompressed form (e.g. +// same formatt as returned by eicOpsX509GetPublicKey()). +// +bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Checks that |signature| is a signature of some data (given by |digest|), +// signed by the public key given by |publicKey|. +// +// The key must be an EC key in uncompressed form (e.g. same format as returned +// by eicOpsX509GetPublicKey()). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// The size of digest must match the size of the key. +// +bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize, + const uint8_t* signature, size_t signatureSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Validates that the passed in data constitutes a valid auth- and verification tokens. +// +bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac, + size_t macSize, uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimeStamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, size_t verificationTokenMacSize); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c new file mode 100644 index 0000000000..d3f5556f66 --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.c @@ -0,0 +1,728 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EicPresentation.h" + +#include <inttypes.h> + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]) { + uint8_t credentialKeys[52]; + + eicMemSet(ctx, '\0', sizeof(EicPresentation)); + + if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys, + 80, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) { + eicDebug("Error decrypting CredentialKeys"); + return false; + } + + // It's supposed to look like this; + // + // CredentialKeys = [ + // bstr, ; storageKey, a 128-bit AES key + // bstr ; credentialPrivKey, the private key for credentialKey + // ] + // + // where storageKey is 16 bytes and credentialPrivateKey is 32 bytes. + // + // So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements + // and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be + // a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20. + // + if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 || + credentialKeys[19] != 0x20) { + eicDebug("Invalid CBOR for CredentialKeys"); + return false; + } + eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE); + eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE); + ctx->testCredential = testCredential; + return true; +} + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now, + uint8_t* publicKeyCert, size_t* publicKeyCertSize, + uint8_t signingKeyBlob[60]) { + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; + uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE]; + + if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) { + eicDebug("Error creating signing key"); + return false; + } + + const int secondsInOneYear = 365 * 24 * 60 * 60; + time_t validityNotBefore = now; + time_t validityNotAfter = now + secondsInOneYear; // One year from now. + if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1, + "Android Identity Credential Key", // issuer CN + "Android Identity Credential Authentication Key", // subject CN + validityNotBefore, validityNotAfter, publicKeyCert, publicKeyCertSize)) { + eicDebug("Error creating certificate for signing key"); + return false; + } + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + eicDebug("Error getting random"); + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv), + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), signingKeyBlob)) { + eicDebug("Error encrypting signing key"); + return false; + } + + return true; +} + +bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) { + uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) { + eicDebug("Error creating ephemeral key"); + return false; + } + eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE); + return true; +} + +bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge) { + do { + if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) { + eicDebug("Failed generating random challenge"); + return false; + } + } while (ctx->authChallenge == 0); + eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge); + *authChallenge = ctx->authChallenge; + return true; +} + +// From "COSE Algorithms" registry +// +#define COSE_ALG_ECDSA_256 -7 + +bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t* requestMessage, size_t requestMessageSize, + int coseSignAlg, + const uint8_t* readerSignatureOfToBeSigned, + size_t readerSignatureOfToBeSignedSize) { + if (ctx->readerPublicKeySize == 0) { + eicDebug("No public key for reader"); + return false; + } + + // Right now we only support ECDSA with SHA-256 (e.g. ES256). + // + if (coseSignAlg != COSE_ALG_ECDSA_256) { + eicDebug( + "COSE Signature algorithm for reader signature is %d, " + "only ECDSA with SHA-256 is supported right now", + coseSignAlg); + return false; + } + + // What we're going to verify is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + // So we're going to build that CBOR... + // + EicCbor cbor; + eicCborInit(&cbor, NULL, 0); + eicCborAppendArray(&cbor, 4); + eicCborAppendString(&cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // External_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time... the CBOR to be written is + // + // ReaderAuthentication = [ + // "ReaderAuthentication", + // SessionTranscript, + // ItemsRequestBytes + // ] + // + // ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) + // + // ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication) + // + // which is easily calculated below + // + size_t calculatedSize = 0; + calculatedSize += 1; // Array of size 3 + calculatedSize += 1; // "ReaderAuthentication" less than 24 bytes + calculatedSize += sizeof("ReaderAuthentication") - 1; // Don't include trailing NUL + calculatedSize += sessionTranscriptSize; // Already CBOR encoded + calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize); + calculatedSize += requestMessageSize; + + // However note that we're authenticating ReaderAuthenticationBytes which + // is a tagged bstr of the bytes of ReaderAuthentication. So need to get + // that in front. + size_t rabCalculatedSize = 0; + rabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize); + rabCalculatedSize += calculatedSize; + + // Begin the bytestring for ReaderAuthenticationBytes; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize); + + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + + // Begins the bytestring for ReaderAuthentication; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize); + + // And now that we know the size, let's fill it in... + // + size_t payloadOffset = cbor.size; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3); + eicCborAppendString(&cbor, "ReaderAuthentication"); + eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize); + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize); + eicCborAppend(&cbor, requestMessage, requestMessageSize); + + if (cbor.size != payloadOffset + calculatedSize) { + eicDebug("CBOR size is %zd but we expected %zd", cbor.size, payloadOffset + calculatedSize); + return false; + } + uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, toBeSignedDigest); + + if (!eicOpsEcDsaVerifyWithPublicKey( + toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned, + readerSignatureOfToBeSignedSize, ctx->readerPublicKey, ctx->readerPublicKeySize)) { + eicDebug("Request message is not signed by public key"); + return false; + } + ctx->requestMessageValidated = true; + return true; +} + +// Validates the next certificate in the reader certificate chain. +bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509, + size_t certX509Size) { + // If we had a previous certificate, use its public key to validate this certificate. + if (ctx->readerPublicKeySize > 0) { + if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size, ctx->readerPublicKey, + ctx->readerPublicKeySize)) { + eicDebug("Certificate is not signed by public key in the previous certificate"); + return false; + } + } + + // Store the key of this certificate, this is used to validate the next certificate + // and also ACPs with certificates that use the same public key... + ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE; + if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey, + &ctx->readerPublicKeySize)) { + eicDebug("Error extracting public key from certificate"); + return false; + } + if (ctx->readerPublicKeySize == 0) { + eicDebug("Zero-length public key in certificate"); + return false; + } + + return true; +} + +bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId, + uint64_t authenticatorId, int hardwareAuthenticatorType, + uint64_t timeStamp, const uint8_t* mac, size_t macSize, + uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, + size_t verificationTokenMacSize) { + if (!eicOpsValidateAuthToken( + challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac, + macSize, verificationTokenChallenge, verificationTokenTimestamp, + verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) { + return false; + } + ctx->authTokenChallenge = challenge; + ctx->authTokenSecureUserId = secureUserId; + ctx->authTokenTimestamp = timeStamp; + ctx->verificationTokenTimestamp = verificationTokenTimestamp; + return true; +} + +static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId) { + if (!userAuthenticationRequired) { + return true; + } + + if (secureUserId != ctx->authTokenSecureUserId) { + eicDebug("secureUserId in profile differs from userId in authToken"); + return false; + } + + if (timeoutMillis == 0) { + if (ctx->authTokenChallenge == 0) { + eicDebug("No challenge in authToken"); + return false; + } + + // If we didn't create a challenge, too bad but user auth with + // timeoutMillis set to 0 needs it. + if (ctx->authChallenge == 0) { + eicDebug("No challenge was created for this session"); + return false; + } + if (ctx->authTokenChallenge != ctx->authChallenge) { + eicDebug("Challenge in authToken (%" PRIu64 + ") doesn't match the challenge " + "that was created (%" PRIu64 ") for this session", + ctx->authTokenChallenge, ctx->authChallenge); + return false; + } + } + + uint64_t now = ctx->verificationTokenTimestamp; + if (ctx->authTokenTimestamp > now) { + eicDebug("Timestamp in authToken is in the future"); + return false; + } + + if (timeoutMillis > 0) { + if (now > ctx->authTokenTimestamp + timeoutMillis) { + eicDebug("Deadline for authToken is in the past"); + return false; + } + } + + return true; +} + +static bool checkReaderAuth(EicPresentation* ctx, const uint8_t* readerCertificate, + size_t readerCertificateSize) { + uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE]; + size_t publicKeySize; + + if (readerCertificateSize == 0) { + return true; + } + + // Remember in this case certificate equality is done by comparing public + // keys, not bitwise comparison of the certificates. + // + publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE; + if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize, publicKey, + &publicKeySize)) { + eicDebug("Error extracting public key from certificate"); + return false; + } + if (publicKeySize == 0) { + eicDebug("Zero-length public key in certificate"); + return false; + } + + if ((ctx->readerPublicKeySize != publicKeySize) || + (eicCryptoMemCmp(ctx->readerPublicKey, publicKey, ctx->readerPublicKeySize) != 0)) { + return false; + } + return true; +} + +// Note: This function returns false _only_ if an error occurred check for access, _not_ +// whether access is granted. Whether access is granted is returned in |accessGranted|. +// +bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, const uint8_t mac[28], + bool* accessGranted) { + *accessGranted = false; + + if (id < 0 || id >= 32) { + eicDebug("id value of %d is out of allowed range [0, 32[", id); + return false; + } + + // Validate the MAC + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer, cborBuilder.size, + NULL)) { + eicDebug("MAC for AccessControlProfile doesn't match"); + return false; + } + + bool passedUserAuth = + checkUserAuth(ctx, userAuthenticationRequired, timeoutMillis, secureUserId); + bool passedReaderAuth = checkReaderAuth(ctx, readerCertificate, readerCertificateSize); + + ctx->accessControlProfileMaskValidated |= (1 << id); + if (readerCertificateSize > 0) { + ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id); + } + if (!passedReaderAuth) { + ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id); + } + if (!passedUserAuth) { + ctx->accessControlProfileMaskFailedUserAuth |= (1 << id); + } + + if (passedUserAuth && passedReaderAuth) { + *accessGranted = true; + eicDebug("Access granted for id %d", id); + } + return true; +} + +bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKeyBlob[60], const char* docType, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize) { + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType, + eicStrLen(docType), signingKeyPriv)) { + eicDebug("Error decrypting signingKeyBlob"); + return false; + } + + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]; + if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) { + eicDebug("ECDH failed"); + return false; + } + + EicCbor cbor; + eicCborInit(&cbor, NULL, 0); + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize); + uint8_t salt[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, salt); + + const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'}; + uint8_t derivedKey[32]; + if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info), + derivedKey, sizeof(derivedKey))) { + eicDebug("HKDF failed"); + return false; + } + + eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey)); + ctx->buildCbor = true; + + // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced + // structure which looks like the following: + // + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "MAC0"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time... the CBOR to be written is + // + // DeviceAuthentication = [ + // "DeviceAuthentication", + // SessionTranscript, + // DocType, ; DocType as used in Documents structure in OfflineResponse + // DeviceNameSpacesBytes + // ] + // + // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + // + // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication) + // + // which is easily calculated below + // + size_t calculatedSize = 0; + calculatedSize += 1; // Array of size 4 + calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes + calculatedSize += sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL + calculatedSize += sessionTranscriptSize; // Already CBOR encoded + size_t docTypeLen = eicStrLen(docType); + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen; + calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize); + calculatedSize += expectedDeviceNamespacesSize; + + // However note that we're authenticating DeviceAuthenticationBytes which + // is a tagged bstr of the bytes of DeviceAuthentication. So need to get + // that in front. + size_t dabCalculatedSize = 0; + dabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize); + dabCalculatedSize += calculatedSize; + + // Begin the bytestring for DeviceAuthenticationBytes; + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize); + + eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + + // Begins the bytestring for DeviceAuthentication; + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize); + + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "DeviceAuthentication"); + eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize); + eicCborAppendString(&ctx->cbor, docType); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize); + ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size; + + eicCborAppendMap(&ctx->cbor, numNamespacesWithValues); + return true; +} + +bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) { + // HAL may use this object multiple times to retrieve data so need to reset various + // state objects here. + ctx->requestMessageValidated = false; + ctx->buildCbor = false; + ctx->accessControlProfileMaskValidated = 0; + ctx->accessControlProfileMaskUsesReaderAuth = 0; + ctx->accessControlProfileMaskFailedReaderAuth = 0; + ctx->accessControlProfileMaskFailedUserAuth = 0; + ctx->readerPublicKeySize = 0; + return true; +} + +EicAccessCheckResult eicPresentationStartRetrieveEntryValue( + EicPresentation* ctx, const char* nameSpace, const char* name, + unsigned int newNamespaceNumEntries, int32_t /* entrySize */, + const int* accessControlProfileIds, size_t numAccessControlProfileIds, + uint8_t* scratchSpace, size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + if (newNamespaceNumEntries > 0) { + eicCborAppendString(&ctx->cbor, nameSpace); + eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries); + } + + // We'll need to calc and store a digest of additionalData to check that it's the same + // additionalData being passed in for every eicPresentationRetrieveEntryValue() call... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + ctx->additionalDataSha256)) { + return EIC_ACCESS_CHECK_RESULT_FAILED; + } + + if (numAccessControlProfileIds == 0) { + return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES; + } + + // Access is granted if at least one of the profiles grants access. + // + // If an item is configured without any profiles, access is denied. + // + EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED; + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + int id = accessControlProfileIds[n]; + uint32_t idBitMask = (1 << id); + + // If the access control profile wasn't validated, this is an error and we + // fail immediately. + bool validated = ((ctx->accessControlProfileMaskValidated & idBitMask) != 0); + if (!validated) { + eicDebug("No ACP for profile id %d", id); + return EIC_ACCESS_CHECK_RESULT_FAILED; + } + + // Otherwise, we _did_ validate the profile. If none of the checks + // failed, we're done + bool failedUserAuth = ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0); + bool failedReaderAuth = ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0); + if (!failedUserAuth && !failedReaderAuth) { + result = EIC_ACCESS_CHECK_RESULT_OK; + break; + } + // One of the checks failed, convey which one + if (failedUserAuth) { + result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED; + } else { + result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED; + } + } + eicDebug("Result %d for name %s", result, name); + + if (result == EIC_ACCESS_CHECK_RESULT_OK) { + eicCborAppendString(&ctx->cbor, name); + } + return result; +} + +// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes. +bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent, + size_t encryptedContentSize, uint8_t* content, + const char* nameSpace, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent, encryptedContentSize, + additionalDataCbor, additionalDataCborSize, content)) { + eicDebug("Error decrypting content"); + return false; + } + + eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28); + + return true; +} + +bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced, + size_t* digestToBeMacedSize) { + if (!ctx->buildCbor) { + *digestToBeMacedSize = 0; + return true; + } + if (*digestToBeMacedSize != 32) { + return false; + } + + // This verifies that the correct expectedDeviceNamespacesSize value was + // passed in at eicPresentationCalcMacKey() time. + if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) { + eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd); + return false; + } + eicCborFinal(&ctx->cbor, digestToBeMaced); + return true; +} + +bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType, + size_t proofOfDeletionCborSize, + uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + EicCbor cbor; + + eicCborInit(&cbor, NULL, 0); + + // What we're going to sign is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&cbor, 4); + eicCborAppendString(&cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize); + + // Finally, the CBOR that we're actually signing. + eicCborAppendArray(&cbor, 3); + eicCborAppendString(&cbor, "ProofOfDeletion"); + eicCborAppendString(&cbor, docType); + eicCborAppendBool(&cbor, ctx->testCredential); + + uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, cborSha256); + if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) { + eicDebug("Error signing proofOfDeletion"); + return false; + } + + return true; +} diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h new file mode 100644 index 0000000000..d79896212e --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +// The maximum size we support for public keys in reader certificates. +#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65 + +typedef struct { + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + // The challenge generated with eicPresentationCreateAuthChallenge() + uint64_t authChallenge; + + // Set by eicPresentationSetAuthToken() and contains the fields + // from the passed in authToken and verificationToken. + // + uint64_t authTokenChallenge; + uint64_t authTokenSecureUserId; + uint64_t authTokenTimestamp; + uint64_t verificationTokenTimestamp; + + // The public key for the reader. + // + // (During the process of pushing reader certificates, this is also used to store + // the public key of the previously pushed certificate.) + // + uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE]; + size_t readerPublicKeySize; + + // This is set to true only if eicPresentationValidateRequestMessage() successfully + // validated the requestMessage. + // + // Why even record this? Because there's no requirement the HAL actually calls that + // function and we validate ACPs before it's called... so it's possible that a + // compromised HAL could trick us into marking ACPs as authorized while they in fact + // aren't. + bool requestMessageValidated; + bool buildCbor; + + // Set to true initialized as a test credential. + bool testCredential; + + // These are bitmasks indicating which of the possible 32 access control profiles are + // authorized. They are built up by eicPresentationValidateAccessControlProfile(). + // + uint32_t accessControlProfileMaskValidated; // True if the profile was validated. + uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using reader auth + uint32_t accessControlProfileMaskFailedReaderAuth; // True if failed reader auth + uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + size_t expectedCborSizeAtEnd; + EicCbor cbor; +} EicPresentation; + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]); + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now, + uint8_t* publicKeyCert, size_t* publicKeyCertSize, + uint8_t signingKeyBlob[60]); + +// Create an ephemeral key-pair. +// +// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in +// |ephemeralPrivateKey|. +// +bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]); + +// Returns a non-zero challenge in |authChallenge|. +bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge); + +// Starts retrieveing entries. +// +bool eicPresentationStartRetrieveEntries(EicPresentation* ctx); + +// Sets the auth-token. +bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId, + uint64_t authenticatorId, int hardwareAuthenticatorType, + uint64_t timeStamp, const uint8_t* mac, size_t macSize, + uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimeStamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, + size_t verificationTokenMacSize); + +// Function to push certificates in the reader certificate chain. +// +// This should start with the root certificate (e.g. the last in the chain) and +// continue up the chain, ending with the certificate for the reader. +// +// Calls to this function should be interleaved with calls to the +// eicPresentationValidateAccessControlProfile() function, see below. +// +bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509, + size_t certX509Size); + +// Checks an access control profile. +// +// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out). +// +// Returns in |accessGranted| whether access is granted. +// +// If |readerCertificate| is non-empty and the public key of one of those +// certificates appear in the chain presented by the reader, this function must +// be called after pushing that certificate using +// eicPresentationPushReaderCert(). +// +bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, const uint8_t mac[28], + bool* accessGranted); + +// Validates that the given requestMessage is signed by the public key in the +// certificate last set with eicPresentationPushReaderCert(). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// Must be called after eicPresentationPushReaderCert() have been used to push +// the final certificate. Which is the certificate of the reader itself. +// +bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t* requestMessage, size_t requestMessageSize, + int coseSignAlg, + const uint8_t* readerSignatureOfToBeSigned, + size_t readerSignatureOfToBeSignedSize); + +typedef enum { + // Returned if access is granted. + EIC_ACCESS_CHECK_RESULT_OK, + + // Returned if an error occurred checking for access. + EIC_ACCESS_CHECK_RESULT_FAILED, + + // Returned if access was denied because item is configured without any + // access control profiles. + EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES, + + // Returned if access was denied because of user authentication. + EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED, + + // Returned if access was denied because of reader authentication. + EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED, +} EicAccessCheckResult; + +// Passes enough information to calculate the MACing key +// +bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKeyBlob[60], const char* docType, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize); + +// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024 +// bytes, the bigger the better). It's done this way to avoid allocating stack +// space. +// +EicAccessCheckResult eicPresentationStartRetrieveEntryValue( + EicPresentation* ctx, const char* nameSpace, const char* name, + unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize); + +// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent, + size_t encryptedContentSize, uint8_t* content, + const char* nameSpace, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute +// and Verify a MAC". +bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced, + size_t* digestToBeMacedSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfDeletion CBOR. +// +bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType, + size_t proofOfDeletionCborSize, + uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c new file mode 100644 index 0000000000..f16605cfad --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.c @@ -0,0 +1,290 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EicProvisioning.h" + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) { + eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + ctx->testCredential = testCredential; + if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) { + return false; + } + + return true; +} + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize) { + if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize, + applicationId, applicationIdSize, ctx->testCredential, + publicKeyCert, publicKeyCertSize)) { + return false; + } + return true; +} + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningSize) { + if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) { + return false; + } + if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) { + return false; + } + + ctx->numEntryCounts = numEntryCounts; + if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) { + return false; + } + for (size_t n = 0; n < numEntryCounts; n++) { + if (entryCounts[n] >= 256) { + return false; + } + ctx->entryCounts[n] = entryCounts[n]; + } + ctx->curNamespace = -1; + ctx->curNamespaceNumProcessed = 0; + + eicCborInit(&ctx->cbor, NULL, 0); + + // What we're going to sign is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize); + ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size; + + eicCborAppendArray(&ctx->cbor, 5); + eicCborAppendString(&ctx->cbor, "ProofOfProvisioning"); + eicCborAppendString(&ctx->cbor, docType); + + eicCborAppendArray(&ctx->cbor, accessControlProfileCount); + + return true; +} + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]) { + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + + // Calculate and return MAC + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer, + cborBuilder.size, outMac)) { + return false; + } + + // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build + // it again. + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, + 0 /* secureUserId */)) { + return false; + } + + // Append the CBOR from the local builder to the digester. + eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size); + + return true; +} + +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + // We'll need to calc and store a digest of additionalData to check that it's the same + // additionalData being passed in for every eicProvisioningAddEntryValue() call... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + ctx->additionalDataSha256)) { + return false; + } + + if (ctx->curNamespace == -1) { + ctx->curNamespace = 0; + ctx->curNamespaceNumProcessed = 0; + // Opens the main map: { * Namespace => [ + Entry ] } + eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts); + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) { + ctx->curNamespace += 1; + ctx->curNamespaceNumProcessed = 0; + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + eicCborAppendMap(&ctx->cbor, 3); + eicCborAppendString(&ctx->cbor, "name"); + eicCborAppendString(&ctx->cbor, name); + + ctx->curEntrySize = entrySize; + ctx->curEntryNumBytesReceived = 0; + + eicCborAppendString(&ctx->cbor, "value"); + + ctx->curNamespaceNumProcessed += 1; + return true; +} + +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + eicCborAppend(&ctx->cbor, content, contentSize); + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor, + additionalDataCborSize, outEncryptedContent)) { + return false; + } + + // If done with this entry, close the map + ctx->curEntryNumBytesReceived += contentSize; + if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) { + eicCborAppendString(&ctx->cbor, "accessControlProfiles"); + eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]); + } + } + return true; +} + +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE]; + + eicCborAppendBool(&ctx->cbor, ctx->testCredential); + eicCborFinal(&ctx->cbor, cborSha256); + + // This verifies that the correct expectedProofOfProvisioningSize value was + // passed in at eicStartPersonalization() time. + if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) { + eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd); + return false; + } + + if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) { + eicDebug("Error signing proofOfProvisioning"); + return false; + } + + return true; +} + +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]) { + EicCbor cbor; + uint8_t cborBuf[52]; + + eicCborInit(&cbor, cborBuf, sizeof(cborBuf)); + eicCborAppendArray(&cbor, 2); + eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE); + eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE); + if (cbor.size > sizeof(cborBuf)) { + eicDebug("Exceeded buffer size"); + return false; + } + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + eicDebug("Error getting random"); + return false; + } + if (!eicOpsEncryptAes128Gcm( + eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) { + eicDebug("Error encrypting CredentialKeys"); + return false; + } + + return true; +} diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h new file mode 100644 index 0000000000..836d16e444 --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.h @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +#define EIC_MAX_NUM_NAMESPACES 32 +#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32 + +typedef struct { + // Set by eicCreateCredentialKey. + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + int numEntryCounts; + uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES]; + + int curNamespace; + int curNamespaceNumProcessed; + + size_t curEntrySize; + size_t curEntryNumBytesReceived; + + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + + size_t expectedCborSizeAtEnd; + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + EicCbor cbor; + + bool testCredential; +} EicProvisioning; + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential); + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize); + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningingSize); + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]); + +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The outEncryptedContent array must be contentSize + 28 bytes long. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfProvisioninging CBOR. +// +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// +// +// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType) +// where +// +// CredentialKeys = [ +// bstr, ; storageKey, a 128-bit AES key +// bstr ; credentialPrivKey, the private key for credentialKey +// ] +// +// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the +// encoded CBOR for CredentialKeys is 52 bytes and consequently +// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes. +// +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h new file mode 100644 index 0000000000..88abef808f --- /dev/null +++ b/identity/aidl/default/libeic/libeic.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H +#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce + * library users to include only this file. All public interfaces, and + * only public interfaces, must be included here. + */ +#define EIC_INSIDE_LIBEIC_H +#include "EicCbor.h" +#include "EicOps.h" +#include "EicPresentation.h" +#include "EicProvisioning.h" +#undef EIC_INSIDE_LIBEIC_H + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H |