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