diff options
author | David Zeuthen <zeuthen@google.com> | 2020-05-11 14:04:54 -0400 |
---|---|---|
committer | David Zeuthen <zeuthen@google.com> | 2021-01-05 18:30:59 -0500 |
commit | 630de2a93e48d8f9ed2a23806d46b7a7a6b46c74 (patch) | |
tree | 7af50ea784609a5f340dd82ae7c386aae610668c /identity/aidl/default/libeic/EicProvisioning.c | |
parent | 19086060541a2a812e76921d3d6a6bdb4f97c521 (diff) |
Identity Credential: Switch default implementation to use libeic.
Introduce platform-neutral C library ("libeic") which can be used to
implement an Identity Credential Trusted Application/Applet in Secure
Hardware.
The libeic library is intentionally low-level, has no dependencies
(not even libc), uses very little run-time memory (less than 500 bytes
during a provisioning or presentation session), and doesn't
dynamically allocate any memory. Crypto routines are provided by the
library user through a simple crypto interface defined in EicOps.
Also provide an Android-side HAL implementation designed to
communicate with libeic running in Secure Hardware outside
Android. Abstract out communications between HAL and TA in a couple of
SecureHardwareProxy* classes which mimic libeic 1:1.
The default implementation of the HAL is a combination of the
aforementioned HAL using libeic in-process backed by BoringSSL for the
crypto bits.
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Bug: 170146643
Change-Id: I3bf43fa7fd9362f94023052591801f2094a04607
Diffstat (limited to 'identity/aidl/default/libeic/EicProvisioning.c')
-rw-r--r-- | identity/aidl/default/libeic/EicProvisioning.c | 290 |
1 files changed, 290 insertions, 0 deletions
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; +} |