/*
 * 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 <inttypes.h>

bool eicPresentationInit(EicPresentation* ctx, 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));

  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);
  }
  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 == 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);
  eicCborAppendStringZ(&cbor, "Signature1");

  // The COSE Encoded protected headers is just a single field with
  // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicity 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;
}

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) {
  // It doesn't make sense to accept any tokens if
  // eicPresentationCreateAuthChallenge() was never called.
  if (ctx->authChallenge == 0) {
    eicDebug(
        "Trying 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 != ctx->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)) {
    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) {
    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, 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) {
  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 simplicity 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 simplicity 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 simplicity 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;
}
