platform_hardware_interfaces/identity/aidl/default/libeic/EicPresentation.c
David Zeuthen 1eb12b2972 identity: Add multi-document presentation support.
This new IPresentationSession interface enables an application to do a
multi-document presentation, something which isn't possible with the
existing API. As a practical example of this consider presenting both
your Mobile Driving License and your Vaccination Certificate in a single
transaction.

Bug: 197965513
Test: New CTS tests and new screen in CtsVerifier
Change-Id: I11712dca35df7f1224debf454731bc17ea9bfb37
2022-01-10 15:12:33 -05:00

962 lines
39 KiB
C

/*
* 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 "EicCommon.h"
#include "EicSession.h"
#include <inttypes.h>
// Global used for assigning ids for presentation objects.
//
static uint32_t gPresentationLastIdAssigned = 0;
bool eicPresentationInit(EicPresentation* ctx, uint32_t sessionId, bool testCredential,
const char* docType, size_t docTypeLength,
const uint8_t* encryptedCredentialKeys,
size_t encryptedCredentialKeysSize) {
uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101];
bool expectPopSha256 = false;
// For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
// bytes (the additional data is the ProofOfProvisioning SHA-256). We need
// to support loading all feature versions.
//
if (encryptedCredentialKeysSize == EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 + 28) {
/* do nothing */
} else if (encryptedCredentialKeysSize == EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 + 28) {
expectPopSha256 = true;
} else {
eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
return false;
}
eicMemSet(ctx, '\0', sizeof(EicPresentation));
ctx->sessionId = sessionId;
if (!eicNextId(&gPresentationLastIdAssigned)) {
eicDebug("Error getting id for object");
return false;
}
ctx->id = gPresentationLastIdAssigned;
if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
encryptedCredentialKeysSize,
// DocType is the additionalAuthenticatedData
(const uint8_t*)docType, docTypeLength, credentialKeys)) {
eicDebug("Error decrypting CredentialKeys");
return false;
}
// It's supposed to look like this;
//
// Feature version 202009:
//
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr, ; credentialPrivKey, the private key for credentialKey
// ]
//
// Feature version 202101:
//
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr, ; credentialPrivKey, the private key for credentialKey
// bstr ; proofOfProvisioning SHA-256
// ]
//
// where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
// SHA-256 is 32 bytes.
//
if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
credentialKeys[1] != 0x50 || // 16-byte bstr
credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) { // 32-byte bstr
eicDebug("Invalid CBOR for CredentialKeys");
return false;
}
if (expectPopSha256) {
if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) { // 32-byte bstr
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;
if (expectPopSha256) {
eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54, EIC_SHA256_DIGEST_SIZE);
}
eicDebug("Initialized presentation with id %" PRIu32, ctx->id);
return true;
}
bool eicPresentationShutdown(EicPresentation* ctx) {
if (ctx->id == 0) {
eicDebug("Trying to shut down presentation with id 0");
return false;
}
eicDebug("Shut down presentation with id %" PRIu32, ctx->id);
eicMemSet(ctx, '\0', sizeof(EicPresentation));
return true;
}
bool eicPresentationGetId(EicPresentation* ctx, uint32_t* outId) {
*outId = ctx->id;
return true;
}
bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType,
size_t docTypeLength, 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];
uint8_t cborBuf[64];
// Generate the ProofOfBinding CBOR to include in the X.509 certificate in
// IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
// by the following CDDL
//
// ProofOfBinding = [
// "ProofOfBinding",
// bstr, // Contains the SHA-256 of ProofOfProvisioning
// ]
//
// This array may grow in the future if other information needs to be
// conveyed.
//
// The bytes of ProofOfBinding is is represented as an OCTET_STRING
// and stored at OID 1.3.6.1.4.1.11129.2.1.26.
//
EicCbor cbor;
eicCborInit(&cbor, cborBuf, sizeof cborBuf);
eicCborAppendArray(&cbor, 2);
eicCborAppendStringZ(&cbor, "ProofOfBinding");
eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256, EIC_SHA256_DIGEST_SIZE);
if (cbor.size > sizeof(cborBuf)) {
eicDebug("Exceeded buffer size");
return false;
}
const uint8_t* proofOfBinding = cborBuf;
size_t proofOfBindingSize = cbor.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, proofOfBinding, proofOfBindingSize,
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, docTypeLength, 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 == EIC_KM_AUTH_CHALLENGE_UNSET);
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->sessionId != 0) {
EicSession* session = eicSessionGetForId(ctx->sessionId);
if (session == NULL) {
eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId);
return false;
}
EicSha256Ctx sha256;
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
eicOpsSha256Init(&sha256);
eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize);
eicOpsSha256Final(&sha256, sessionTranscriptSha256);
if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256,
EIC_SHA256_DIGEST_SIZE) != 0) {
eicDebug("SessionTranscript mismatch");
return false;
}
}
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);
eicCborAppendStringZ(&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);
eicCborAppendStringZ(&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;
}
static bool getChallenge(EicPresentation* ctx, uint64_t* outAuthChallenge) {
// Use authChallenge from session if applicable.
*outAuthChallenge = ctx->authChallenge;
if (ctx->sessionId != 0) {
EicSession* session = eicSessionGetForId(ctx->sessionId);
if (session == NULL) {
eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId);
return false;
}
*outAuthChallenge = session->authChallenge;
}
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) {
uint64_t authChallenge;
if (!getChallenge(ctx, &authChallenge)) {
return false;
}
// It doesn't make sense to accept any tokens if eicPresentationCreateAuthChallenge()
// was never called.
if (authChallenge == EIC_KM_AUTH_CHALLENGE_UNSET) {
eicDebug("Trying to validate tokens when no auth-challenge was previously generated");
return false;
}
// At least the verification-token must have the same challenge as what was generated.
if (verificationTokenChallenge != authChallenge) {
eicDebug("Challenge in verification token does not match the challenge "
"previously generated");
return false;
}
if (!eicOpsValidateAuthToken(
challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac,
macSize, verificationTokenChallenge, verificationTokenTimestamp,
verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) {
eicDebug("Error validating authToken");
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;
}
// Only ACP with auth-on-every-presentation - those with timeout == 0 - need the
// challenge to match...
if (timeoutMillis == 0) {
uint64_t authChallenge;
if (!getChallenge(ctx, &authChallenge)) {
return false;
}
if (ctx->authTokenChallenge != authChallenge) {
eicDebug("Challenge in authToken (%" PRIu64
") doesn't match the challenge "
"that was created (%" PRIu64 ") for this session",
ctx->authTokenChallenge, 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,
uint8_t* scratchSpace,
size_t scratchSpaceSize) {
*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
EicCbor cborBuilder;
eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
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 |= (1U << id);
if (readerCertificateSize > 0) {
ctx->accessControlProfileMaskUsesReaderAuth |= (1U << id);
}
if (!passedReaderAuth) {
ctx->accessControlProfileMaskFailedReaderAuth |= (1U << id);
}
if (!passedUserAuth) {
ctx->accessControlProfileMaskFailedUserAuth |= (1U << 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,
size_t docTypeLength, unsigned int numNamespacesWithValues,
size_t expectedDeviceNamespacesSize) {
if (ctx->sessionId != 0) {
EicSession* session = eicSessionGetForId(ctx->sessionId);
if (session == NULL) {
eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId);
return false;
}
EicSha256Ctx sha256;
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
eicOpsSha256Init(&sha256);
eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize);
eicOpsSha256Final(&sha256, sessionTranscriptSha256);
if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256,
EIC_SHA256_DIGEST_SIZE) != 0) {
eicDebug("SessionTranscript mismatch");
return false;
}
readerEphemeralPublicKey = session->readerEphemeralPublicKey;
}
uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
docTypeLength, 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);
eicCborAppendStringZ(&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
calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLength) + docTypeLength;
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);
eicCborAppendStringZ(&ctx->cbor, "DeviceAuthentication");
eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
eicCborAppendString(&ctx->cbor, docType, docTypeLength);
// 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, size_t nameSpaceLength,
const char* name, size_t nameLength,
unsigned int newNamespaceNumEntries, int32_t entrySize,
const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
uint8_t* scratchSpace, size_t scratchSpaceSize) {
(void)entrySize;
uint8_t* additionalDataCbor = scratchSpace;
size_t additionalDataCborBufferSize = scratchSpaceSize;
size_t additionalDataCborSize;
if (newNamespaceNumEntries > 0) {
eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
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...
//
ctx->accessCheckOk = false;
if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
nameSpace, nameSpaceLength, name, nameLength,
additionalDataCbor, additionalDataCborBufferSize,
&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, nameLength);
ctx->accessCheckOk = true;
}
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, size_t nameSpaceLength,
const char* name, size_t nameLength,
const uint8_t* accessControlProfileIds,
size_t numAccessControlProfileIds,
uint8_t* scratchSpace,
size_t scratchSpaceSize) {
uint8_t* additionalDataCbor = scratchSpace;
size_t additionalDataCborBufferSize = scratchSpaceSize;
size_t additionalDataCborSize;
uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
nameSpace, nameSpaceLength, name, nameLength,
additionalDataCbor, additionalDataCborBufferSize,
&additionalDataCborSize,
calculatedSha256)) {
return false;
}
if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
eicDebug("SHA-256 mismatch of additionalData");
return false;
}
if (!ctx->accessCheckOk) {
eicDebug("Attempting to retrieve a value for which access is not granted");
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 docTypeLength,
const uint8_t* challenge, size_t challengeSize,
bool includeChallenge,
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);
eicCborAppendStringZ(&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, includeChallenge ? 4 : 3);
eicCborAppendStringZ(&cbor, "ProofOfDeletion");
eicCborAppendString(&cbor, docType, docTypeLength);
if (includeChallenge) {
eicCborAppendByteString(&cbor, challenge, challengeSize);
}
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;
}
bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType,
size_t docTypeLength, bool testCredential,
const uint8_t* challenge, size_t challengeSize,
size_t proofOfOwnershipCborSize,
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);
eicCborAppendStringZ(&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, proofOfOwnershipCborSize);
// Finally, the CBOR that we're actually signing.
eicCborAppendArray(&cbor, 4);
eicCborAppendStringZ(&cbor, "ProofOfOwnership");
eicCborAppendString(&cbor, docType, docTypeLength);
eicCborAppendByteString(&cbor, challenge, challengeSize);
eicCborAppendBool(&cbor, 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;
}