/*
 * 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;
}

// Helper used to append the DeviceAuthencation prelude, used for both MACing and ECDSA signing.
static size_t appendDeviceAuthentication(EicCbor* cbor, const uint8_t* sessionTranscript,
                                         size_t sessionTranscriptSize, const char* docType,
                                         size_t docTypeLength,
                                         size_t expectedDeviceNamespacesSize) {
    // 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(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);

    eicCborAppendSemantic(cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);

    // Begins the bytestring for DeviceAuthentication;
    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);

    eicCborAppendArray(cbor, 4);
    eicCborAppendStringZ(cbor, "DeviceAuthentication");
    eicCborAppend(cbor, sessionTranscript, sessionTranscriptSize);
    eicCborAppendString(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(cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
    size_t expectedCborSizeAtEnd = expectedDeviceNamespacesSize + cbor->size;

    return expectedCborSizeAtEnd;
}

bool eicPresentationPrepareDeviceAuthentication(
        EicPresentation* ctx, const uint8_t* sessionTranscript, size_t sessionTranscriptSize,
        const uint8_t* readerEphemeralPublicKey, size_t readerEphemeralPublicKeySize,
        const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
        unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
    if (ctx->sessionId != 0) {
        if (readerEphemeralPublicKeySize != 0) {
            eicDebug("In a session but readerEphemeralPublicKeySize is non-zero");
            return false;
        }
        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;
        readerEphemeralPublicKeySize = session->readerEphemeralPublicKeySize;
    }

    // Stash the decrypted DeviceKey in context since we'll need it later in
    // eicPresentationFinishRetrievalWithSignature()
    if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
                                docTypeLength, ctx->deviceKeyPriv)) {
        eicDebug("Error decrypting signingKeyBlob");
        return false;
    }

    // We can only do MACing if EReaderKey has been set... it might not have been set if for
    // example mdoc session encryption isn't in use. In that case we can still do ECDSA
    if (readerEphemeralPublicKeySize > 0) {
        if (readerEphemeralPublicKeySize != EIC_P256_PUB_KEY_SIZE) {
            eicDebug("Unexpected size %zd for readerEphemeralPublicKeySize",
                     readerEphemeralPublicKeySize);
            return false;
        }

        uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
        if (!eicOpsEcdh(readerEphemeralPublicKey, ctx->deviceKeyPriv, 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));

        // 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));

        // Append DeviceAuthentication prelude and open the DeviceSigned map...
        ctx->expectedCborSizeAtEnd =
                appendDeviceAuthentication(&ctx->cbor, sessionTranscript, sessionTranscriptSize,
                                           docType, docTypeLength, expectedDeviceNamespacesSize);
        eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
        ctx->buildCbor = true;
    }

    // Now do the same for ECDSA signatures...
    //
    eicCborInit(&ctx->cborEcdsa, NULL, 0);
    eicCborAppendArray(&ctx->cborEcdsa, 4);
    eicCborAppendStringZ(&ctx->cborEcdsa, "Signature1");
    static const uint8_t coseEncodedProtectedHeadersEcdsa[] = {0xa1, 0x01, 0x26};
    eicCborAppendByteString(&ctx->cborEcdsa, coseEncodedProtectedHeadersEcdsa,
                            sizeof(coseEncodedProtectedHeadersEcdsa));
    static const uint8_t externalAadEcdsa[0] = {};
    eicCborAppendByteString(&ctx->cborEcdsa, externalAadEcdsa, sizeof(externalAadEcdsa));

    // Append DeviceAuthentication prelude and open the DeviceSigned map...
    ctx->expectedCborEcdsaSizeAtEnd =
            appendDeviceAuthentication(&ctx->cborEcdsa, sessionTranscript, sessionTranscriptSize,
                                       docType, docTypeLength, expectedDeviceNamespacesSize);
    eicCborAppendMap(&ctx->cborEcdsa, numNamespacesWithValues);
    ctx->buildCborEcdsa = true;

    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->buildCborEcdsa = 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);

        eicCborAppendString(&ctx->cborEcdsa, nameSpace, nameSpaceLength);
        eicCborAppendMap(&ctx->cborEcdsa, 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);
        eicCborAppendString(&ctx->cborEcdsa, 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);
    eicCborAppend(&ctx->cborEcdsa, 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 eicPresentationFinishRetrievalWithSignature(EicPresentation* ctx, uint8_t* digestToBeMaced,
                                                 size_t* digestToBeMacedSize,
                                                 uint8_t* signatureOfToBeSigned,
                                                 size_t* signatureOfToBeSignedSize) {
    if (!eicPresentationFinishRetrieval(ctx, digestToBeMaced, digestToBeMacedSize)) {
        return false;
    }

    if (!ctx->buildCborEcdsa) {
        *signatureOfToBeSignedSize = 0;
        return true;
    }
    if (*signatureOfToBeSignedSize != EIC_ECDSA_P256_SIGNATURE_SIZE) {
        return false;
    }

    // This verifies that the correct expectedDeviceNamespacesSize value was
    // passed in at eicPresentationCalcMacKey() time.
    if (ctx->cborEcdsa.size != ctx->expectedCborEcdsaSizeAtEnd) {
        eicDebug("CBOR ECDSA size is %zd, was expecting %zd", ctx->cborEcdsa.size,
                 ctx->expectedCborEcdsaSizeAtEnd);
        return false;
    }
    uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
    eicCborFinal(&ctx->cborEcdsa, cborSha256);
    if (!eicOpsEcDsa(ctx->deviceKeyPriv, cborSha256, signatureOfToBeSigned)) {
        eicDebug("Error signing DeviceAuthentication");
        return false;
    }
    eicDebug("set the signature");
    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;
}
