diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp index 2eb0faa034..7f342d087a 100644 --- a/identity/aidl/default/Android.bp +++ b/identity/aidl/default/Android.bp @@ -1,3 +1,67 @@ +cc_library_static { + name: "android.hardware.identity-libeic-hal-common", + vendor_available: true, + srcs: [ + "common/IdentityCredential.cpp", + "common/IdentityCredentialStore.cpp", + "common/WritableIdentityCredential.cpp", + ], + export_include_dirs: [ + "common", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "liblog", + "libcrypto", + "libbinder_ndk", + "libkeymaster_messages", + ], + static_libs: [ + "libbase", + "libcppbor", + "libutils", + "libsoft_attestation_cert", + "libkeymaster_portable", + "libsoft_attestation_cert", + "libpuresoftkeymasterdevice", + "android.hardware.identity-support-lib", + "android.hardware.identity-ndk_platform", + "android.hardware.keymaster-ndk_platform", + ], +} + +cc_library_static { + name: "android.hardware.identity-libeic-library", + vendor_available: true, + srcs: [ + "libeic/EicCbor.c", + "libeic/EicPresentation.c", + "libeic/EicProvisioning.c", + "EicOpsImpl.cc", + ], + export_include_dirs: [ + "libeic", + ], + cflags: [ + "-DEIC_COMPILATION", + "-Wall", + "-Wextra", + "-DEIC_DEBUG", + // Allow using C2x extensions such as omitting parameter names + "-Wno-c2x-extensions", + ], + shared_libs: [ + "libbase", + "libcrypto", + ], + static_libs: [ + "android.hardware.identity-support-lib", + ], +} + cc_binary { name: "android.hardware.identity-service.example", relative_install_path: "hw", @@ -7,23 +71,30 @@ cc_binary { cflags: [ "-Wall", "-Wextra", + "-g", ], shared_libs: [ - "libbase", - "libbinder_ndk", - "libcppbor", - "libcrypto", "liblog", + "libcrypto", + "libbinder_ndk", + "libkeymaster_messages", + ], + static_libs: [ + "libbase", + "libcppbor", "libutils", + "libsoft_attestation_cert", + "libkeymaster_portable", + "libsoft_attestation_cert", + "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", "android.hardware.identity-ndk_platform", "android.hardware.keymaster-ndk_platform", + "android.hardware.identity-libeic-hal-common", + "android.hardware.identity-libeic-library", ], srcs: [ - "IdentityCredential.cpp", - "IdentityCredentialStore.cpp", - "WritableIdentityCredential.cpp", - "Util.cpp", "service.cpp", + "FakeSecureHardwareProxy.cpp", ], } diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc new file mode 100644 index 0000000000..3f2ec8b763 --- /dev/null +++ b/identity/aidl/default/EicOpsImpl.cc @@ -0,0 +1,506 @@ +/* + * 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. + */ + +#define LOG_TAG "EicOpsImpl" + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "EicOps.h" + +using ::std::optional; +using ::std::string; +using ::std::tuple; +using ::std::vector; + +void* eicMemSet(void* s, int c, size_t n) { + return memset(s, c, n); +} + +void* eicMemCpy(void* dest, const void* src, size_t n) { + return memcpy(dest, src, n); +} + +size_t eicStrLen(const char* s) { + return strlen(s); +} + +int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) { + return CRYPTO_memcmp(s1, s2, n); +} + +void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + HMAC_CTX_init(realCtx); + if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) != 1) { + LOG(ERROR) << "Error initializing HMAC_CTX"; + } +} + +void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + if (HMAC_Update(realCtx, data, len) != 1) { + LOG(ERROR) << "Error updating HMAC_CTX"; + } +} + +void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + unsigned int size = 0; + if (HMAC_Final(realCtx, digest, &size) != 1) { + LOG(ERROR) << "Error finalizing HMAC_CTX"; + } + if (size != EIC_SHA256_DIGEST_SIZE) { + LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size; + } +} + +void eicOpsSha256Init(EicSha256Ctx* ctx) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Init(realCtx); +} + +void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Update(realCtx, data, len); +} + +void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Final(digest, realCtx); +} + +bool eicOpsRandom(uint8_t* buf, size_t numBytes) { + optional> bytes = ::android::hardware::identity::support::getRandom(numBytes); + if (!bytes.has_value()) { + return false; + } + memcpy(buf, bytes.value().data(), numBytes); + return true; +} + +bool eicOpsEncryptAes128Gcm( + const uint8_t* key, // Must be 16 bytes + const uint8_t* nonce, // Must be 12 bytes + const uint8_t* data, // May be NULL if size is 0 + size_t dataSize, + const uint8_t* additionalAuthenticationData, // May be NULL if size is 0 + size_t additionalAuthenticationDataSize, uint8_t* encryptedData) { + vector cppKey; + cppKey.resize(16); + memcpy(cppKey.data(), key, 16); + + vector cppData; + cppData.resize(dataSize); + if (dataSize > 0) { + memcpy(cppData.data(), data, dataSize); + } + + vector cppAAD; + cppAAD.resize(additionalAuthenticationDataSize); + if (additionalAuthenticationDataSize > 0) { + memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize); + } + + vector cppNonce; + cppNonce.resize(12); + memcpy(cppNonce.data(), nonce, 12); + + optional> cppEncryptedData = + android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce, cppData, + cppAAD); + if (!cppEncryptedData.has_value()) { + return false; + } + + memcpy(encryptedData, cppEncryptedData.value().data(), cppEncryptedData.value().size()); + return true; +} + +// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|, +// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28. +// +// The format of |encryptedData| must be as specified in the +// encryptAes128Gcm() function. +bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes + const uint8_t* encryptedData, size_t encryptedDataSize, + const uint8_t* additionalAuthenticationData, + size_t additionalAuthenticationDataSize, uint8_t* data) { + vector keyVec; + keyVec.resize(16); + memcpy(keyVec.data(), key, 16); + + vector encryptedDataVec; + encryptedDataVec.resize(encryptedDataSize); + if (encryptedDataSize > 0) { + memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize); + } + + vector aadVec; + aadVec.resize(additionalAuthenticationDataSize); + if (additionalAuthenticationDataSize > 0) { + memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize); + } + + optional> decryptedDataVec = + android::hardware::identity::support::decryptAes128Gcm(keyVec, encryptedDataVec, + aadVec); + if (!decryptedDataVec.has_value()) { + eicDebug("Error decrypting data"); + return false; + } + if (decryptedDataVec.value().size() != encryptedDataSize - 28) { + eicDebug("Decrypted data is size %zd, expected %zd", decryptedDataVec.value().size(), + encryptedDataSize - 28); + return false; + } + + if (decryptedDataVec.value().size() > 0) { + memcpy(data, decryptedDataVec.value().data(), decryptedDataVec.value().size()); + } + return true; +} + +bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) { + optional> keyPair = android::hardware::identity::support::createEcKeyPair(); + if (!keyPair) { + eicDebug("Error creating EC keypair"); + return false; + } + optional> privKey = + android::hardware::identity::support::ecKeyPairGetPrivateKey(keyPair.value()); + if (!privKey) { + eicDebug("Error extracting private key"); + return false; + } + if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) { + eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE); + return false; + } + + optional> pubKey = + android::hardware::identity::support::ecKeyPairGetPublicKey(keyPair.value()); + if (!pubKey) { + eicDebug("Error extracting public key"); + return false; + } + // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04. + if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) { + eicDebug("Private key is %zd bytes long, expected %zd", pubKey.value().size(), + (size_t)EIC_P256_PRIV_KEY_SIZE + 1); + return false; + } + + memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE); + memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE); + + return true; +} + +bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, bool testCredential, uint8_t* cert, + size_t* certSize) { + vector challengeVec(challengeSize); + memcpy(challengeVec.data(), challenge, challengeSize); + + vector applicationIdVec(applicationIdSize); + memcpy(applicationIdVec.data(), applicationId, applicationIdSize); + + optional, vector>>> ret = + android::hardware::identity::support::createEcKeyPairAndAttestation( + challengeVec, applicationIdVec, testCredential); + if (!ret) { + eicDebug("Error generating CredentialKey and attestation"); + return false; + } + + // Extract certificate chain. + vector flatChain = + android::hardware::identity::support::certificateChainJoin(ret.value().second); + if (*certSize < flatChain.size()) { + eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize, + flatChain.size()); + return false; + } + memcpy(cert, flatChain.data(), flatChain.size()); + *certSize = flatChain.size(); + + // Extract private key. + optional> privKey = + android::hardware::identity::support::ecKeyPairGetPrivateKey(ret.value().first); + if (!privKey) { + eicDebug("Error extracting private key"); + return false; + } + if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) { + eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE); + return false; + } + + memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE); + + return true; +} + +bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial, + const char* issuerName, const char* subjectName, time_t validityNotBefore, + time_t validityNotAfter, uint8_t* cert, + size_t* certSize) { // inout + vector signingKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE); + + vector pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1); + pubKeyVec[0] = 0x04; + memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE); + + std::string serialDecimal = android::base::StringPrintf("%d", serial); + + optional> certVec = + android::hardware::identity::support::ecPublicKeyGenerateCertificate( + pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName, + validityNotBefore, validityNotAfter); + if (!certVec) { + eicDebug("Error generating certificate"); + return false; + } + + if (*certSize < certVec.value().size()) { + eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize, + certVec.value().size()); + return false; + } + memcpy(cert, certVec.value().data(), certVec.value().size()); + *certSize = certVec.value().size(); + + return true; +} + +bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE], + uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + vector privKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE); + + vector digestVec(EIC_SHA256_DIGEST_SIZE); + memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE); + + optional> derSignature = + android::hardware::identity::support::signEcDsaDigest(privKeyVec, digestVec); + if (!derSignature) { + eicDebug("Error signing data"); + return false; + } + + ECDSA_SIG* sig; + const unsigned char* p = derSignature.value().data(); + sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size()); + if (sig == nullptr) { + eicDebug("Error decoding DER signature"); + return false; + } + + if (BN_bn2binpad(sig->r, signature, 32) != 32) { + eicDebug("Error encoding r"); + return false; + } + if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) { + eicDebug("Error encoding s"); + return false; + } + + return true; +} + +static const uint8_t hbkTest[16] = {0}; +static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + +const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) { + if (testCredential) { + return hbkTest; + } + return hbkReal; +} + +bool eicOpsValidateAuthToken(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 */) { + // Here's where we would validate the passed-in |authToken| to assure ourselves + // that it comes from the e.g. biometric hardware and wasn't made up by an attacker. + // + // However this involves calculating the MAC which requires access to the to + // a pre-shared key which we don't have... + // + return true; +} + +bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey, + size_t* publicKeySize) { + vector chain; + chain.resize(x509CertSize); + memcpy(chain.data(), x509Cert, x509CertSize); + optional> res = + android::hardware::identity::support::certificateChainGetTopMostKey(chain); + if (!res) { + return false; + } + if (res.value().size() > *publicKeySize) { + eicDebug("Public key size is %zd but buffer only has room for %zd bytes", + res.value().size(), *publicKeySize); + return false; + } + *publicKeySize = res.value().size(); + memcpy(publicKey, res.value().data(), *publicKeySize); + eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert", *publicKeySize, + x509CertSize); + return true; +} + +bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize, + const uint8_t* publicKey, size_t publicKeySize) { + vector certVec(x509Cert, x509Cert + x509CertSize); + vector publicKeyVec(publicKey, publicKey + publicKeySize); + return android::hardware::identity::support::certificateSignedByPublicKey(certVec, + publicKeyVec); +} + +bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize, + const uint8_t* signature, size_t signatureSize, + const uint8_t* publicKey, size_t publicKeySize) { + vector digestVec(digest, digest + digestSize); + vector signatureVec(signature, signature + signatureSize); + vector publicKeyVec(publicKey, publicKey + publicKeySize); + + vector derSignature; + if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(signatureVec, + derSignature)) { + LOG(ERROR) << "Error convering signature to DER format"; + return false; + } + + if (!android::hardware::identity::support::checkEcDsaSignature(digestVec, derSignature, + publicKeyVec)) { + LOG(ERROR) << "Signature check failed"; + return false; + } + return true; +} + +bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE], + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) { + vector pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1); + pubKeyVec[0] = 0x04; + memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE); + + vector privKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE); + + optional> shared = + android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec); + if (!shared) { + LOG(ERROR) << "Error performing ECDH"; + return false; + } + if (shared.value().size() != EIC_P256_COORDINATE_SIZE) { + LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size() << " expected " + << EIC_P256_COORDINATE_SIZE << " bytes"; + return false; + } + memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE); + return true; +} + +bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt, + size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output, + size_t outputSize) { + vector sharedSecretVec(sharedSecretSize); + memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize); + vector saltVec(saltSize); + memcpy(saltVec.data(), salt, saltSize); + vector infoVec(infoSize); + memcpy(infoVec.data(), info, infoSize); + + optional> result = android::hardware::identity::support::hkdf( + sharedSecretVec, saltVec, infoVec, outputSize); + if (!result) { + LOG(ERROR) << "Error performing HKDF"; + return false; + } + if (result.value().size() != outputSize) { + LOG(ERROR) << "Unexpected size of HKDF " << result.value().size() << " expected " + << outputSize; + return false; + } + memcpy(output, result.value().data(), outputSize); + return true; +} + +#ifdef EIC_DEBUG + +void eicPrint(const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) { + vector dataVec(dataSize); + memcpy(dataVec.data(), data, dataSize); + android::hardware::identity::support::hexdump(message, dataVec); +} + +void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize) { + vector cborDataVec(cborDataSize); + memcpy(cborDataVec.data(), cborData, cborDataSize); + string str = + android::hardware::identity::support::cborPrettyPrint(cborDataVec, maxBStrSize, {}); + fprintf(stderr, "%s\n", str.c_str()); +} + +#endif // EIC_DEBUG diff --git a/identity/aidl/default/EicOpsImpl.h b/identity/aidl/default/EicOpsImpl.h new file mode 100644 index 0000000000..333cdce63f --- /dev/null +++ b/identity/aidl/default/EicOpsImpl.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H +#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H + +#include +#include +#include + +// Add whatever includes are needed for definitions below. +// + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Set the following defines to match the implementation of the supplied +// eicOps*() operations. See EicOps.h for details. +// + +#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX) + +#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX) + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp new file mode 100644 index 0000000000..de6762fc2e --- /dev/null +++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp @@ -0,0 +1,324 @@ +/* + * 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. + */ + +#define LOG_TAG "FakeSecureHardwareProxy" + +#include "FakeSecureHardwareProxy.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using ::std::optional; +using ::std::string; +using ::std::tuple; +using ::std::vector; + +namespace android::hardware::identity { + +// ---------------------------------------------------------------------- + +FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {} + +FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {} + +bool FakeSecureHardwareProvisioningProxy::shutdown() { + LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; + return true; +} + +bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) { + LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): " + << sizeof(EicProvisioning); + return eicProvisioningInit(&ctx_, testCredential); +} + +// Returns public key certificate. +optional> FakeSecureHardwareProvisioningProxy::createCredentialKey( + const vector& challenge, const vector& applicationId) { + uint8_t publicKeyCert[4096]; + size_t publicKeyCertSize = sizeof publicKeyCert; + if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(), + applicationId.data(), applicationId.size(), + publicKeyCert, &publicKeyCertSize)) { + return {}; + } + vector pubKeyCert(publicKeyCertSize); + memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize); + return pubKeyCert; +} + +bool FakeSecureHardwareProvisioningProxy::startPersonalization( + int accessControlProfileCount, vector entryCounts, const string& docType, + size_t expectedProofOfProvisioningSize) { + if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(), + entryCounts.size(), docType.c_str(), + expectedProofOfProvisioningSize)) { + return false; + } + return true; +} + +// Returns MAC (28 bytes). +optional> FakeSecureHardwareProvisioningProxy::addAccessControlProfile( + int id, const vector& readerCertificate, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) { + vector mac(28); + if (!eicProvisioningAddAccessControlProfile( + &ctx_, id, readerCertificate.data(), readerCertificate.size(), + userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) { + return {}; + } + return mac; +} + +bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector& accessControlProfileIds, + const string& nameSpace, const string& name, + uint64_t entrySize) { + uint8_t scratchSpace[512]; + return eicProvisioningBeginAddEntry(&ctx_, accessControlProfileIds.data(), + accessControlProfileIds.size(), nameSpace.c_str(), + name.c_str(), entrySize, scratchSpace, sizeof scratchSpace); +} + +// Returns encryptedContent. +optional> FakeSecureHardwareProvisioningProxy::addEntryValue( + const vector& accessControlProfileIds, const string& nameSpace, const string& name, + const vector& content) { + vector eicEncryptedContent; + uint8_t scratchSpace[512]; + eicEncryptedContent.resize(content.size() + 28); + if (!eicProvisioningAddEntryValue( + &ctx_, accessControlProfileIds.data(), accessControlProfileIds.size(), + nameSpace.c_str(), name.c_str(), content.data(), content.size(), + eicEncryptedContent.data(), scratchSpace, sizeof scratchSpace)) { + return {}; + } + return eicEncryptedContent; +} + +// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). +optional> FakeSecureHardwareProvisioningProxy::finishAddingEntries() { + vector signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); + if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) { + return {}; + } + return signatureOfToBeSigned; +} + +// Returns encryptedCredentialKeys (80 bytes). +optional> FakeSecureHardwareProvisioningProxy::finishGetCredentialData( + const string& docType) { + vector encryptedCredentialKeys(80); + if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(), + encryptedCredentialKeys.data())) { + return {}; + } + return encryptedCredentialKeys; +} + +// ---------------------------------------------------------------------- + +FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {} + +FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {} + +bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType, + vector encryptedCredentialKeys) { + LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): " + << sizeof(EicPresentation); + return eicPresentationInit(&ctx_, testCredential, docType.c_str(), + encryptedCredentialKeys.data()); +} + +// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) +optional, vector>> +FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) { + uint8_t publicKeyCert[512]; + size_t publicKeyCertSize = sizeof(publicKeyCert); + vector signingKeyBlob(60); + + if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert, + &publicKeyCertSize, signingKeyBlob.data())) { + return {}; + } + + vector cert; + cert.resize(publicKeyCertSize); + memcpy(cert.data(), publicKeyCert, publicKeyCertSize); + + return std::make_pair(cert, signingKeyBlob); +} + +// Returns private key +optional> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() { + vector priv(EIC_P256_PRIV_KEY_SIZE); + if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) { + return {}; + } + return priv; +} + +optional FakeSecureHardwarePresentationProxy::createAuthChallenge() { + uint64_t challenge; + if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) { + return {}; + } + return challenge; +} + +bool FakeSecureHardwarePresentationProxy::shutdown() { + LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; + return true; +} + +bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector& certX509) { + return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size()); +} + +bool FakeSecureHardwarePresentationProxy::validateRequestMessage( + const vector& sessionTranscript, const vector& requestMessage, + int coseSignAlg, const vector& readerSignatureOfToBeSigned) { + return eicPresentationValidateRequestMessage( + &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(), + requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(), + readerSignatureOfToBeSigned.size()); +} + +bool FakeSecureHardwarePresentationProxy::setAuthToken( + uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const vector& mac, + uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, const vector& verificationTokenMac) { + return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId, + hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(), + verificationTokenChallenge, verificationTokenTimestamp, + verificationTokenSecurityLevel, verificationTokenMac.data(), + verificationTokenMac.size()); +} + +optional FakeSecureHardwarePresentationProxy::validateAccessControlProfile( + int id, const vector& readerCertificate, bool userAuthenticationRequired, + int timeoutMillis, uint64_t secureUserId, const vector& mac) { + bool accessGranted = false; + if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(), + readerCertificate.size(), + userAuthenticationRequired, timeoutMillis, + secureUserId, mac.data(), &accessGranted)) { + return {}; + } + return accessGranted; +} + +bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() { + return eicPresentationStartRetrieveEntries(&ctx_); +} + +bool FakeSecureHardwarePresentationProxy::calcMacKey( + const vector& sessionTranscript, const vector& readerEphemeralPublicKey, + const vector& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) { + if (signingKeyBlob.size() != 60) { + eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size()); + return false; + } + return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(), + readerEphemeralPublicKey.data(), signingKeyBlob.data(), + docType.c_str(), numNamespacesWithValues, + expectedProofOfProvisioningSize); +} + +AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector& accessControlProfileIds) { + uint8_t scratchSpace[512]; + EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue( + &ctx_, nameSpace.c_str(), name.c_str(), newNamespaceNumEntries, entrySize, + accessControlProfileIds.data(), accessControlProfileIds.size(), scratchSpace, + sizeof scratchSpace); + switch (result) { + case EIC_ACCESS_CHECK_RESULT_OK: + return AccessCheckResult::kOk; + case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES: + return AccessCheckResult::kNoAccessControlProfiles; + case EIC_ACCESS_CHECK_RESULT_FAILED: + return AccessCheckResult::kFailed; + case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED: + return AccessCheckResult::kUserAuthenticationFailed; + case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED: + return AccessCheckResult::kReaderAuthenticationFailed; + } + eicDebug("Unknown result with code %d, returning kFailed", (int)result); + return AccessCheckResult::kFailed; +} + +optional> FakeSecureHardwarePresentationProxy::retrieveEntryValue( + const vector& encryptedContent, const string& nameSpace, const string& name, + const vector& accessControlProfileIds) { + uint8_t scratchSpace[512]; + vector content; + content.resize(encryptedContent.size() - 28); + if (!eicPresentationRetrieveEntryValue( + &ctx_, encryptedContent.data(), encryptedContent.size(), content.data(), + nameSpace.c_str(), name.c_str(), accessControlProfileIds.data(), + accessControlProfileIds.size(), scratchSpace, sizeof scratchSpace)) { + return {}; + } + return content; +} + +optional> FakeSecureHardwarePresentationProxy::finishRetrieval() { + vector mac(32); + size_t macSize = 32; + if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) { + return {}; + } + mac.resize(macSize); + return mac; +} + +optional> FakeSecureHardwarePresentationProxy::deleteCredential( + const string& docType, size_t proofOfDeletionCborSize) { + vector signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); + if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize, + signatureOfToBeSigned.data())) { + return {}; + } + return signatureOfToBeSigned; +} + +} // namespace android::hardware::identity diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h new file mode 100644 index 0000000000..b858dd409d --- /dev/null +++ b/identity/aidl/default/FakeSecureHardwareProxy.h @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H +#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H + +#include + +#include "SecureHardwareProxy.h" + +namespace android::hardware::identity { + +// This implementation uses libEmbeddedIC in-process. +// +class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy { + public: + FakeSecureHardwareProvisioningProxy(); + virtual ~FakeSecureHardwareProvisioningProxy(); + + bool initialize(bool testCredential) override; + + bool shutdown() override; + + // Returns public key certificate. + optional> createCredentialKey(const vector& challenge, + const vector& applicationId) override; + + bool startPersonalization(int accessControlProfileCount, vector entryCounts, + const string& docType, + size_t expectedProofOfProvisioningSize) override; + + // Returns MAC (28 bytes). + optional> addAccessControlProfile(int id, + const vector& readerCertificate, + bool userAuthenticationRequired, + uint64_t timeoutMillis, + uint64_t secureUserId) override; + + bool beginAddEntry(const vector& accessControlProfileIds, const string& nameSpace, + const string& name, uint64_t entrySize) override; + + // Returns encryptedContent. + optional> addEntryValue(const vector& accessControlProfileIds, + const string& nameSpace, const string& name, + const vector& content) override; + + // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). + optional> finishAddingEntries() override; + + // Returns encryptedCredentialKeys (80 bytes). + optional> finishGetCredentialData(const string& docType) override; + + protected: + EicProvisioning ctx_; +}; + +// This implementation uses libEmbeddedIC in-process. +// +class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy { + public: + FakeSecureHardwarePresentationProxy(); + virtual ~FakeSecureHardwarePresentationProxy(); + + bool initialize(bool testCredential, string docType, + vector encryptedCredentialKeys) override; + + // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) + optional, vector>> generateSigningKeyPair(string docType, + time_t now) override; + + // Returns private key + optional> createEphemeralKeyPair() override; + + optional createAuthChallenge() override; + + bool startRetrieveEntries() override; + + bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const vector& mac, + uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const vector& verificationTokenMac) override; + + bool pushReaderCert(const vector& certX509) override; + + optional validateAccessControlProfile(int id, const vector& readerCertificate, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, + const vector& mac) override; + + bool validateRequestMessage(const vector& sessionTranscript, + const vector& requestMessage, int coseSignAlg, + const vector& readerSignatureOfToBeSigned) override; + + bool calcMacKey(const vector& sessionTranscript, + const vector& readerEphemeralPublicKey, + const vector& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, + size_t expectedProofOfProvisioningSize) override; + + AccessCheckResult startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector& accessControlProfileIds) override; + + optional> retrieveEntryValue( + const vector& encryptedContent, const string& nameSpace, const string& name, + const vector& accessControlProfileIds) override; + + optional> finishRetrieval() override; + + optional> deleteCredential(const string& docType, + size_t proofOfDeletionCborSize) override; + + bool shutdown() override; + + protected: + EicPresentation ctx_; +}; + +// Factory implementation. +// +class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory { + public: + FakeSecureHardwareProxyFactory() {} + virtual ~FakeSecureHardwareProxyFactory() {} + + sp createProvisioningProxy() override { + return new FakeSecureHardwareProvisioningProxy(); + } + + sp createPresentationProxy() override { + return new FakeSecureHardwarePresentationProxy(); + } +}; + +} // namespace android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp deleted file mode 100644 index 66b9f13d89..0000000000 --- a/identity/aidl/default/Util.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2019, 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. - */ - -#define LOG_TAG "Util" - -#include "Util.h" - -#include - -#include - -#include - -#include -#include - -namespace aidl::android::hardware::identity { - -using namespace ::android::hardware::identity; - -// This is not a very random HBK but that's OK because this is the SW -// implementation where it can't be kept secret. -vector hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - -const vector& getHardwareBoundKey() { - return hardwareBoundKey; -} - -vector secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) { - cppbor::Map map; - map.add("id", profile.id); - - if (profile.readerCertificate.encodedCertificate.size() > 0) { - map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate.encodedCertificate)); - } - - if (profile.userAuthenticationRequired) { - map.add("userAuthenticationRequired", profile.userAuthenticationRequired); - map.add("timeoutMillis", profile.timeoutMillis); - map.add("secureUserId", profile.secureUserId); - } - - return map.encode(); -} - -optional> secureAccessControlProfileCalcMac( - const SecureAccessControlProfile& profile, const vector& storageKey) { - vector cborData = secureAccessControlProfileEncodeCbor(profile); - - optional> nonce = support::getRandom(12); - if (!nonce) { - return {}; - } - optional> macO = - support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData); - if (!macO) { - return {}; - } - return macO.value(); -} - -bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, - const vector& storageKey) { - vector cborData = secureAccessControlProfileEncodeCbor(profile); - - if (profile.mac.size() < support::kAesGcmIvSize) { - return false; - } - vector nonce = - vector(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize); - optional> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData); - if (!mac) { - return false; - } - if (mac.value() != profile.mac) { - return false; - } - return true; -} - -vector entryCreateAdditionalData(const string& nameSpace, const string& name, - const vector accessControlProfileIds) { - cppbor::Map map; - map.add("Namespace", nameSpace); - map.add("Name", name); - - cppbor::Array acpIds; - for (auto id : accessControlProfileIds) { - acpIds.add(id); - } - map.add("AccessControlProfileIds", std::move(acpIds)); - return map.encode(); -} - -} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h deleted file mode 100644 index 9fccba2c72..0000000000 --- a/identity/aidl/default/Util.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019, 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. - */ - -#ifndef ANDROID_HARDWARE_IDENTITY_UTIL_H -#define ANDROID_HARDWARE_IDENTITY_UTIL_H - -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace aidl::android::hardware::identity { - -using ::std::optional; -using ::std::string; -using ::std::vector; - -// Returns the hardware-bound AES-128 key. -const vector& getHardwareBoundKey(); - -// Calculates the MAC for |profile| using |storageKey|. -optional> secureAccessControlProfileCalcMac( - const SecureAccessControlProfile& profile, const vector& storageKey); - -// Checks authenticity of the MAC in |profile| using |storageKey|. -bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, - const vector& storageKey); - -// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method. -vector entryCreateAdditionalData(const string& nameSpace, const string& name, - const vector accessControlProfileIds); - -} // namespace aidl::android::hardware::identity - -#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp similarity index 65% rename from identity/aidl/default/IdentityCredential.cpp rename to identity/aidl/default/common/IdentityCredential.cpp index dfcd4f557f..270fcfa8d0 100644 --- a/identity/aidl/default/IdentityCredential.cpp +++ b/identity/aidl/default/common/IdentityCredential.cpp @@ -18,7 +18,6 @@ #include "IdentityCredential.h" #include "IdentityCredentialStore.h" -#include "Util.h" #include @@ -30,6 +29,8 @@ #include #include +#include "FakeSecureHardwareProxy.h" + namespace aidl::android::hardware::identity { using ::aidl::android::hardware::keymaster::Timestamp; @@ -69,40 +70,17 @@ int IdentityCredential::initialize() { docType_ = docTypeItem->value(); testCredential_ = testCredentialItem->value(); - vector hardwareBoundKey; - if (testCredential_) { - hardwareBoundKey = support::getTestHardwareBoundKey(); - } else { - hardwareBoundKey = getHardwareBoundKey(); - } - const vector& encryptedCredentialKeys = encryptedCredentialKeysItem->value(); - const vector docTypeVec(docType_.begin(), docType_.end()); - optional> decryptedCredentialKeys = - support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec); - if (!decryptedCredentialKeys) { - LOG(ERROR) << "Error decrypting CredentialKeys"; + + if (encryptedCredentialKeys.size() != 80) { + LOG(ERROR) << "Unexpected size for encrypted CredentialKeys"; return IIdentityCredentialStore::STATUS_INVALID_DATA; } - auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value()); - if (dckItem == nullptr) { - LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage; - return IIdentityCredentialStore::STATUS_INVALID_DATA; + if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) { + LOG(ERROR) << "hwProxy->initialize failed"; + return false; } - const cppbor::Array* dckArrayItem = dckItem->asArray(); - if (dckArrayItem == nullptr || dckArrayItem->size() != 2) { - LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements"; - return IIdentityCredentialStore::STATUS_INVALID_DATA; - } - const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr(); - const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr(); - if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) { - LOG(ERROR) << "CredentialKeys unexpected item types"; - return IIdentityCredentialStore::STATUS_INVALID_DATA; - } - storageKey_ = storageKeyItem->value(); - credentialPrivKey_ = credentialPrivKeyItem->value(); return IIdentityCredentialStore::STATUS_OK; } @@ -110,12 +88,20 @@ int IdentityCredential::initialize() { ndk::ScopedAStatus IdentityCredential::deleteCredential( vector* outProofOfDeletionSignature) { cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_}; - vector proofOfDeletion = array.encode(); + vector proofOfDeletionCbor = array.encode(); + vector podDigest = support::sha256(proofOfDeletionCbor); - optional> signature = support::coseSignEcDsa(credentialPrivKey_, - proofOfDeletion, // payload - {}, // additionalData - {}); // certificateChain + optional> signatureOfToBeSigned = + hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size()); + if (!signatureOfToBeSigned) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion")); + } + + optional> signature = + support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(), + proofOfDeletionCbor, // data + {}); // certificateChain if (!signature) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); @@ -126,22 +112,28 @@ ndk::ScopedAStatus IdentityCredential::deleteCredential( } ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector* outKeyPair) { - optional> kp = support::createEcKeyPair(); - if (!kp) { + optional> ephemeralPriv = hwProxy_->createEphemeralKeyPair(); + if (!ephemeralPriv) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair")); + IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key")); + } + optional> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value()); + if (!keyPair) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair")); } // Stash public key of this key-pair for later check in startRetrieval(). - optional> publicKey = support::ecKeyPairGetPublicKey(kp.value()); + optional> publicKey = support::ecKeyPairGetPublicKey(keyPair.value()); if (!publicKey) { + LOG(ERROR) << "Error getting public part of ephemeral key pair"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error getting public part of ephemeral key pair")); } ephemeralPublicKey_ = publicKey.value(); - *outKeyPair = kp.value(); + *outKeyPair = keyPair.value(); return ndk::ScopedAStatus::ok(); } @@ -152,109 +144,15 @@ ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey( } ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) { - uint64_t challenge = 0; - while (challenge == 0) { - optional> bytes = support::getRandom(8); - if (!bytes) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting random data for challenge")); - } - - challenge = 0; - for (size_t n = 0; n < bytes.value().size(); n++) { - challenge |= ((bytes.value())[n] << (n * 8)); - } + optional challenge = hwProxy_->createAuthChallenge(); + if (!challenge) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge")); } - - *outChallenge = challenge; - authChallenge_ = challenge; + *outChallenge = challenge.value(); return ndk::ScopedAStatus::ok(); } -// TODO: this could be a lot faster if we did all the splitting and pubkey extraction -// ahead of time. -bool checkReaderAuthentication(const SecureAccessControlProfile& profile, - const vector& readerCertificateChain) { - optional> acpPubKey = - support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate); - if (!acpPubKey) { - LOG(ERROR) << "Error extracting public key from readerCertificate in profile"; - return false; - } - - optional>> certificatesInChain = - support::certificateChainSplit(readerCertificateChain); - if (!certificatesInChain) { - LOG(ERROR) << "Error splitting readerCertificateChain"; - return false; - } - for (const vector& certInChain : certificatesInChain.value()) { - optional> certPubKey = support::certificateChainGetTopMostKey(certInChain); - if (!certPubKey) { - LOG(ERROR) - << "Error extracting public key from certificate in chain presented by reader"; - return false; - } - if (acpPubKey.value() == certPubKey.value()) { - return true; - } - } - return false; -} - -bool checkUserAuthentication(const SecureAccessControlProfile& profile, - const VerificationToken& verificationToken, - const HardwareAuthToken& authToken, uint64_t authChallenge) { - if (profile.secureUserId != authToken.userId) { - LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId - << ") differs from userId in authToken (" << authToken.userId << ")"; - return false; - } - - if (verificationToken.timestamp.milliSeconds == 0) { - LOG(ERROR) << "VerificationToken is not set"; - return false; - } - if (authToken.timestamp.milliSeconds == 0) { - LOG(ERROR) << "AuthToken is not set"; - return false; - } - - if (profile.timeoutMillis == 0) { - if (authToken.challenge == 0) { - LOG(ERROR) << "No challenge in authToken"; - return false; - } - - if (authToken.challenge != int64_t(authChallenge)) { - LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") " - << "doesn't match the challenge we created (" << authChallenge << ")"; - return false; - } - return true; - } - - // Timeout-based user auth follows. The verification token conveys what the - // time is right now in the environment which generated the auth token. This - // is what makes it possible to do timeout-based checks. - // - const Timestamp now = verificationToken.timestamp; - if (authToken.timestamp.milliSeconds > now.milliSeconds) { - LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds - << ") is in the future (now: " << now.milliSeconds << ")"; - return false; - } - if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) { - LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + " - << profile.timeoutMillis << " = " - << (authToken.timestamp.milliSeconds + profile.timeoutMillis) - << ") is in the past (now: " << now.milliSeconds << ")"; - return false; - } - return true; -} - ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces( const vector& requestNamespaces) { requestNamespaces_ = requestNamespaces; @@ -284,6 +182,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } if (numStartRetrievalCalls_ > 0) { if (sessionTranscript_ != sessionTranscript) { + LOG(ERROR) << "Session Transcript changed"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH, "Passed-in SessionTranscript doesn't match previously used SessionTranscript")); @@ -291,6 +190,40 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } sessionTranscript_ = sessionTranscript; + // This resets various state in the TA... + if (!hwProxy_->startRetrieveEntries()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries")); + } + + optional> signatureOfToBeSigned; + if (readerSignature.size() > 0) { + signatureOfToBeSigned = support::coseSignGetSignature(readerSignature); + if (!signatureOfToBeSigned) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting signatureOfToBeSigned from COSE_Sign1")); + } + } + + // Feed the auth token to secure hardware. + if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId, + int(authToken.authenticatorType), authToken.timestamp.milliSeconds, + authToken.mac, verificationToken_.challenge, + verificationToken_.timestamp.milliSeconds, + int(verificationToken_.securityLevel), verificationToken_.mac)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token")); + } + + // We'll be feeding ACPs interleaved with certificates from the reader + // certificate chain... + vector remainingAcps = accessControlProfiles; + + // ... and we'll use those ACPs to build up a 32-bit mask indicating which + // of the possible 32 ACPs grants access. + uint32_t accessControlProfileMask = 0; + // If there is a signature, validate that it was made with the top-most key in the // certificate chain embedded in the COSE_Sign1 structure. optional> readerCertificateChain; @@ -302,45 +235,113 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "Unable to get reader certificate chain from COSE_Sign1")); } - if (!support::certificateChainValidate(readerCertificateChain.value())) { + // First, feed all the reader certificates to the secure hardware. We start + // at the end.. + optional>> splitCerts = + support::certificateChainSplit(readerCertificateChain.value()); + if (!splitCerts || splitCerts.value().size() == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "Error validating reader certificate chain")); + "Error splitting certificate chain from COSE_Sign1")); + } + for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) { + const vector& x509Cert = splitCerts.value()[n]; + if (!hwProxy_->pushReaderCert(x509Cert)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + StringPrintf("Error validating reader certificate %zd", n).c_str())); + } + + // If we have ACPs for that particular certificate, send them to the + // TA right now... + // + // Remember in this case certificate equality is done by comparing public keys, + // not bitwise comparison of the certificates. + // + optional> x509CertPubKey = + support::certificateChainGetTopMostKey(x509Cert); + if (!x509CertPubKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + StringPrintf("Error getting public key from reader certificate %zd", n) + .c_str())); + } + vector::iterator it = remainingAcps.begin(); + while (it != remainingAcps.end()) { + const SecureAccessControlProfile& profile = *it; + if (profile.readerCertificate.encodedCertificate.size() == 0) { + ++it; + continue; + } + optional> profilePubKey = support::certificateChainGetTopMostKey( + profile.readerCertificate.encodedCertificate); + if (!profilePubKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error getting public key from profile")); + } + if (profilePubKey.value() == x509CertPubKey.value()) { + optional res = hwProxy_->validateAccessControlProfile( + profile.id, profile.readerCertificate.encodedCertificate, + profile.userAuthenticationRequired, profile.timeoutMillis, + profile.secureUserId, profile.mac); + if (!res) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Error validating access control profile")); + } + if (res.value()) { + accessControlProfileMask |= (1 << profile.id); + } + it = remainingAcps.erase(it); + } else { + ++it; + } + } } - optional> readerPublicKey = - support::certificateChainGetTopMostKey(readerCertificateChain.value()); - if (!readerPublicKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "Unable to get public key from reader certificate chain")); - } - - const vector& itemsRequestBytes = itemsRequest; - vector encodedReaderAuthentication = - cppbor::Array() - .add("ReaderAuthentication") - .add(std::move(sessionTranscriptItem)) - .add(cppbor::Semantic(24, itemsRequestBytes)) - .encode(); - vector encodedReaderAuthenticationBytes = - cppbor::Semantic(24, encodedReaderAuthentication).encode(); - if (!support::coseCheckEcDsaSignature(readerSignature, - encodedReaderAuthenticationBytes, // detached content - readerPublicKey.value())) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "readerSignature check failed")); + // ... then pass the request message and have the TA check it's signed by the + // key in last certificate we pushed. + if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) { + optional> tbsSignature = support::coseSignGetSignature(readerSignature); + if (!tbsSignature) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting toBeSigned from COSE_Sign1")); + } + optional coseSignAlg = support::coseSignGetAlg(readerSignature); + if (!coseSignAlg) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting signature algorithm from COSE_Sign1")); + } + if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest, + coseSignAlg.value(), tbsSignature.value())) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "readerMessage is not signed by top-level certificate")); + } } } - // Here's where we would validate the passed-in |authToken| to assure ourselves - // that it comes from the e.g. biometric hardware and wasn't made up by an attacker. - // - // However this involves calculating the MAC. However this requires access - // to the key needed to a pre-shared key which we don't have... - // + // Feed remaining access control profiles... + for (const SecureAccessControlProfile& profile : remainingAcps) { + optional res = hwProxy_->validateAccessControlProfile( + profile.id, profile.readerCertificate.encodedCertificate, + profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId, + profile.mac); + if (!res) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Error validating access control profile")); + } + if (res.value()) { + accessControlProfileMask |= (1 << profile.id); + } + } + // TODO: move this check to the TA +#if 1 // To prevent replay-attacks, we check that the public part of the ephemeral // key we previously created, is present in the DeviceEngagement part of // SessionTranscript as a COSE_Key, in uncompressed form. @@ -364,6 +365,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "SessionTranscript (make sure leading zeroes are not used)")); } } +#endif // itemsRequest: If non-empty, contains request data that may be signed by the // reader. The content can be defined in the way appropriate for the @@ -463,30 +465,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } } - // Validate all the access control profiles in the requestData. - bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0)); - for (const auto& profile : accessControlProfiles) { - if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { - LOG(ERROR) << "Error checking MAC for profile"; - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_INVALID_DATA, - "Error checking MAC for profile")); - } - int accessControlCheck = IIdentityCredentialStore::STATUS_OK; - if (profile.userAuthenticationRequired) { - if (!haveAuthToken || - !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) { - accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED; - } - } else if (profile.readerCertificate.encodedCertificate.size() > 0) { - if (!readerCertificateChain || - !checkReaderAuthentication(profile, readerCertificateChain.value())) { - accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED; - } - } - profileIdToAccessCheckResult_[profile.id] = accessControlCheck; - } - deviceNameSpacesMap_ = cppbor::Map(); currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map(); @@ -496,8 +474,36 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( itemsRequest_ = itemsRequest; signingKeyBlob_ = signingKeyBlob; - // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time. - expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize(); + // calculate the size of DeviceNameSpaces. We need to know it ahead of time. + calcDeviceNameSpacesSize(accessControlProfileMask); + + // Count the number of non-empty namespaces + size_t numNamespacesWithValues = 0; + for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) { + if (expectedNumEntriesPerNamespace_[n] > 0) { + numNamespacesWithValues += 1; + } + } + + // Finally, pass info so the HMAC key can be derived and the TA can start + // creating the DeviceNameSpaces CBOR... + if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) { + // We expect the reader ephemeral public key to be same size and curve + // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH + // won't work. So its length should be 65 bytes and it should be + // starting with 0x04. + if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Reader public key is not in expected format")); + } + vector pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end()); + if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_, + numNamespacesWithValues, expectedDeviceNameSpacesSize_)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries")); + } + } numStartRetrievalCalls_ += 1; return ndk::ScopedAStatus::ok(); @@ -520,7 +526,7 @@ size_t cborNumBytesForTstr(const string& value) { return 1 + cborNumBytesForLength(value.size()) + value.size(); } -size_t IdentityCredential::calcDeviceNameSpacesSize() { +void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) { /* * This is how DeviceNameSpaces is defined: * @@ -539,7 +545,7 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { * encoded. */ size_t ret = 0; - size_t numNamespacesWithValues = 0; + vector numEntriesPerNamespace; for (const RequestNamespace& rns : requestNamespaces_) { vector itemsToInclude; @@ -562,13 +568,9 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { // bool authorized = false; for (auto id : rdi.accessControlProfileIds) { - auto it = profileIdToAccessCheckResult_.find(id); - if (it != profileIdToAccessCheckResult_.end()) { - int accessControlForProfile = it->second; - if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) { - authorized = true; - break; - } + if (accessControlProfileMask & (1 << id)) { + authorized = true; + break; } } if (!authorized) { @@ -578,7 +580,10 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { itemsToInclude.push_back(rdi); } - // If no entries are to be in the namespace, we don't include it... + numEntriesPerNamespace.push_back(itemsToInclude.size()); + + // If no entries are to be in the namespace, we don't include it in + // the CBOR... if (itemsToInclude.size() == 0) { continue; } @@ -597,15 +602,14 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { // that. ret += item.size; } - - numNamespacesWithValues++; } - // Now that we now the nunber of namespaces with values, we know how many + // Now that we know the number of namespaces with values, we know how many // bytes the DeviceNamespaces map in the beginning is going to take up. - ret += 1 + cborNumBytesForLength(numNamespacesWithValues); + ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size()); - return ret; + expectedDeviceNameSpacesSize_ = ret; + expectedNumEntriesPerNamespace_ = numEntriesPerNamespace; } ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( @@ -626,9 +630,11 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( "No more name spaces left to go through")); } + bool newNamespace; if (currentNameSpace_ == "") { // First call. currentNameSpace_ = nameSpace; + newNamespace = true; } if (nameSpace == currentNameSpace_) { @@ -655,6 +661,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( requestCountsRemaining_.erase(requestCountsRemaining_.begin()); currentNameSpace_ = nameSpace; + newNamespace = true; } // It's permissible to have an empty itemsRequest... but if non-empty you can @@ -674,35 +681,52 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( } } - // Enforce access control. - // - // Access is granted if at least one of the profiles grants access. - // - // If an item is configured without any profiles, access is denied. - // - int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES; - for (auto id : accessControlProfileIds) { - auto search = profileIdToAccessCheckResult_.find(id); - if (search == profileIdToAccessCheckResult_.end()) { + unsigned int newNamespaceNumEntries = 0; + if (newNamespace) { + if (expectedNumEntriesPerNamespace_.size() == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, - "Requested entry with unvalidated profile id")); + "No more populated name spaces left to go through")); } - int accessControlForProfile = search->second; - if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) { - accessControl = IIdentityCredentialStore::STATUS_OK; - break; - } - accessControl = accessControlForProfile; - } - if (accessControl != IIdentityCredentialStore::STATUS_OK) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - int(accessControl), "Access control check failed")); + newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0]; + expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin()); } - entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds); + // Access control is enforced in the secure hardware. + // + // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO: + // consolidate). + // + AccessCheckResult res = hwProxy_->startRetrieveEntryValue( + nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds); + switch (res) { + case AccessCheckResult::kOk: + /* Do nothing. */ + break; + case AccessCheckResult::kFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Access control check failed (failed)")); + break; + case AccessCheckResult::kNoAccessControlProfiles: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES, + "Access control check failed (no access control profiles)")); + break; + case AccessCheckResult::kUserAuthenticationFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED, + "Access control check failed (user auth)")); + break; + case AccessCheckResult::kReaderAuthenticationFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED, + "Access control check failed (reader auth)")); + break; + } currentName_ = name; + currentAccessControlProfileIds_ = accessControlProfileIds; entryRemainingBytes_ = entrySize; entryValue_.resize(0); @@ -711,8 +735,8 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector& encryptedContent, vector* outContent) { - optional> content = - support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_); + optional> content = hwProxy_->retrieveEntryValue( + encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_); if (!content) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data")); @@ -777,28 +801,14 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector* outMac, optional> mac; if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0) { - vector docTypeAsBlob(docType_.begin(), docType_.end()); - optional> signingKey = - support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob); - if (!signingKey) { + optional> digestToBeMaced = hwProxy_->finishRetrieval(); + if (!digestToBeMaced || digestToBeMaced.value().size() != 32) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, - "Error decrypting signingKeyBlob")); - } - - vector sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode(); - optional> eMacKey = - support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes); - if (!eMacKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey")); - } - mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces, - eMacKey.value()); - if (!mac) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error MACing data")); + "Error generating digestToBeMaced")); } + // Now construct COSE_Mac0 from the returned MAC... + mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */); } *outMac = mac.value_or(vector({})); @@ -808,56 +818,18 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector* outMac, ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair( vector* outSigningKeyBlob, Certificate* outSigningKeyCertificate) { - string serialDecimal = "1"; - string issuer = "Android Identity Credential Key"; - string subject = "Android Identity Credential Authentication Key"; - time_t validityNotBefore = time(nullptr); - time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600; - - optional> signingKeyPKCS8 = support::createEcKeyPair(); - if (!signingKeyPKCS8) { + time_t now = time(NULL); + optional, vector>> pair = + hwProxy_->generateSigningKeyPair(docType_, now); + if (!pair) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey")); } - optional> signingPublicKey = - support::ecKeyPairGetPublicKey(signingKeyPKCS8.value()); - if (!signingPublicKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting public part of signingKey")); - } - - optional> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value()); - if (!signingKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting private part of signingKey")); - } - - optional> certificate = support::ecPublicKeyGenerateCertificate( - signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject, - validityNotBefore, validityNotAfter); - if (!certificate) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey")); - } - - optional> nonce = support::getRandom(12); - if (!nonce) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error getting random")); - } - vector docTypeAsBlob(docType_.begin(), docType_.end()); - optional> encryptedSigningKey = support::encryptAes128Gcm( - storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob); - if (!encryptedSigningKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey")); - } - *outSigningKeyBlob = encryptedSigningKey.value(); *outSigningKeyCertificate = Certificate(); - outSigningKeyCertificate->encodedCertificate = certificate.value(); + outSigningKeyCertificate->encodedCertificate = pair->first; + + *outSigningKeyBlob = pair->second; return ndk::ScopedAStatus::ok(); } diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h similarity index 88% rename from identity/aidl/default/IdentityCredential.h rename to identity/aidl/default/common/IdentityCredential.h index a8a6409ca0..228182160a 100644 --- a/identity/aidl/default/IdentityCredential.h +++ b/identity/aidl/default/common/IdentityCredential.h @@ -29,10 +29,15 @@ #include +#include "IdentityCredentialStore.h" +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { using ::aidl::android::hardware::keymaster::HardwareAuthToken; using ::aidl::android::hardware::keymaster::VerificationToken; +using ::android::sp; +using ::android::hardware::identity::SecureHardwarePresentationProxy; using ::std::map; using ::std::set; using ::std::string; @@ -40,10 +45,11 @@ using ::std::vector; class IdentityCredential : public BnIdentityCredential { public: - IdentityCredential(const vector& credentialData) - : credentialData_(credentialData), + IdentityCredential(sp hwProxy, + const vector& credentialData) + : hwProxy_(hwProxy), + credentialData_(credentialData), numStartRetrievalCalls_(0), - authChallenge_(0), expectedDeviceNameSpacesSize_(0) {} // Parses and decrypts credentialData_, return a status code from @@ -75,14 +81,13 @@ class IdentityCredential : public BnIdentityCredential { private: // Set by constructor + sp hwProxy_; vector credentialData_; int numStartRetrievalCalls_; // Set by initialize() string docType_; bool testCredential_; - vector storageKey_; - vector credentialPrivKey_; // Set by createEphemeralKeyPair() vector ephemeralPublicKey_; @@ -90,9 +95,6 @@ class IdentityCredential : public BnIdentityCredential { // Set by setReaderEphemeralPublicKey() vector readerPublicKey_; - // Set by createAuthChallenge() - uint64_t authChallenge_; - // Set by setRequestedNamespaces() vector requestNamespaces_; @@ -100,7 +102,6 @@ class IdentityCredential : public BnIdentityCredential { VerificationToken verificationToken_; // Set at startRetrieval() time. - map profileIdToAccessCheckResult_; vector signingKeyBlob_; vector sessionTranscript_; vector itemsRequest_; @@ -111,15 +112,16 @@ class IdentityCredential : public BnIdentityCredential { // Calculated at startRetrieval() time. size_t expectedDeviceNameSpacesSize_; + vector expectedNumEntriesPerNamespace_; // Set at startRetrieveEntryValue() time. string currentNameSpace_; string currentName_; + vector currentAccessControlProfileIds_; size_t entryRemainingBytes_; vector entryValue_; - vector entryAdditionalData_; - size_t calcDeviceNameSpacesSize(); + void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask); }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp similarity index 90% rename from identity/aidl/default/IdentityCredentialStore.cpp rename to identity/aidl/default/common/IdentityCredentialStore.cpp index 30dc6f330b..13f91aacf9 100644 --- a/identity/aidl/default/IdentityCredentialStore.cpp +++ b/identity/aidl/default/common/IdentityCredentialStore.cpp @@ -39,8 +39,9 @@ ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation( ndk::ScopedAStatus IdentityCredentialStore::createCredential( const string& docType, bool testCredential, shared_ptr* outWritableCredential) { + sp hwProxy = hwProxyFactory_->createProvisioningProxy(); shared_ptr wc = - ndk::SharedRefBase::make(docType, testCredential); + ndk::SharedRefBase::make(hwProxy, docType, testCredential); if (!wc->initialize()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, @@ -60,8 +61,9 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential( "Unsupported cipher suite")); } + sp hwProxy = hwProxyFactory_->createPresentationProxy(); shared_ptr credential = - ndk::SharedRefBase::make(credentialData); + ndk::SharedRefBase::make(hwProxy, credentialData); auto ret = credential->initialize(); if (ret != IIdentityCredentialStore::STATUS_OK) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h similarity index 85% rename from identity/aidl/default/IdentityCredentialStore.h rename to identity/aidl/default/common/IdentityCredentialStore.h index 4f3a42139f..d35e632984 100644 --- a/identity/aidl/default/IdentityCredentialStore.h +++ b/identity/aidl/default/common/IdentityCredentialStore.h @@ -19,15 +19,20 @@ #include +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { +using ::android::sp; +using ::android::hardware::identity::SecureHardwareProxyFactory; using ::std::shared_ptr; using ::std::string; using ::std::vector; class IdentityCredentialStore : public BnIdentityCredentialStore { public: - IdentityCredentialStore() {} + IdentityCredentialStore(sp hwProxyFactory) + : hwProxyFactory_(hwProxyFactory) {} // The GCM chunk size used by this implementation is 64 KiB. static constexpr size_t kGcmChunkSize = 64 * 1024; @@ -41,6 +46,9 @@ class IdentityCredentialStore : public BnIdentityCredentialStore { ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector& credentialData, shared_ptr* outCredential) override; + + private: + sp hwProxyFactory_; }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h new file mode 100644 index 0000000000..b89ad8781f --- /dev/null +++ b/identity/aidl/default/common/SecureHardwareProxy.h @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H +#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H + +#include +#include +#include +#include +#include + +namespace android::hardware::identity { + +using ::android::RefBase; +using ::std::optional; +using ::std::pair; +using ::std::string; +using ::std::vector; + +// These classes are used to communicate with Secure Hardware. They mimic the +// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is intended +// to be forwarded to the Secure Hardware. +// +// Instances are instantiated when a provisioning or presentation session +// starts. When the session is complete, the shutdown() method is called. +// + +// Forward declare. +// +class SecureHardwareProvisioningProxy; +class SecureHardwarePresentationProxy; + +// This is a class used to create proxies. +// +class SecureHardwareProxyFactory : public RefBase { + public: + SecureHardwareProxyFactory() {} + virtual ~SecureHardwareProxyFactory() {} + + virtual sp createProvisioningProxy() = 0; + virtual sp createPresentationProxy() = 0; +}; + +// The proxy used for provisioning. +// +class SecureHardwareProvisioningProxy : public RefBase { + public: + SecureHardwareProvisioningProxy() {} + virtual ~SecureHardwareProvisioningProxy() {} + + virtual bool initialize(bool testCredential) = 0; + + // Returns public key certificate chain with attestation. + // + // This must return an entire certificate chain and its implementation must + // be coordinated with the implementation of eicOpsCreateCredentialKey() on + // the TA side (which may return just a single certificate or the entire + // chain). + virtual optional> createCredentialKey(const vector& challenge, + const vector& applicationId) = 0; + + virtual bool startPersonalization(int accessControlProfileCount, vector entryCounts, + const string& docType, + size_t expectedProofOfProvisioningSize) = 0; + + // Returns MAC (28 bytes). + virtual optional> addAccessControlProfile( + int id, const vector& readerCertificate, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) = 0; + + virtual bool beginAddEntry(const vector& accessControlProfileIds, const string& nameSpace, + const string& name, uint64_t entrySize) = 0; + + // Returns encryptedContent. + virtual optional> addEntryValue(const vector& accessControlProfileIds, + const string& nameSpace, const string& name, + const vector& content) = 0; + + // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). + virtual optional> finishAddingEntries() = 0; + + // Returns encryptedCredentialKeys (80 bytes). + virtual optional> finishGetCredentialData(const string& docType) = 0; + + virtual bool shutdown() = 0; +}; + +enum AccessCheckResult { + kOk, + kFailed, + kNoAccessControlProfiles, + kUserAuthenticationFailed, + kReaderAuthenticationFailed, +}; + +// The proxy used for presentation. +// +class SecureHardwarePresentationProxy : public RefBase { + public: + SecureHardwarePresentationProxy() {} + virtual ~SecureHardwarePresentationProxy() {} + + virtual bool initialize(bool testCredential, string docType, + vector encryptedCredentialKeys) = 0; + + // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) + virtual optional, vector>> generateSigningKeyPair(string docType, + time_t now) = 0; + + // Returns private key + virtual optional> createEphemeralKeyPair() = 0; + + virtual optional createAuthChallenge() = 0; + + virtual bool startRetrieveEntries() = 0; + + virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, + const vector& mac, uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const vector& verificationTokenMac) = 0; + + virtual bool pushReaderCert(const vector& certX509) = 0; + + virtual optional validateAccessControlProfile(int id, + const vector& readerCertificate, + bool userAuthenticationRequired, + int timeoutMillis, uint64_t secureUserId, + const vector& mac) = 0; + + virtual bool validateRequestMessage(const vector& sessionTranscript, + const vector& requestMessage, int coseSignAlg, + const vector& readerSignatureOfToBeSigned) = 0; + + virtual bool calcMacKey(const vector& sessionTranscript, + const vector& readerEphemeralPublicKey, + const vector& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, + size_t expectedProofOfProvisioningSize) = 0; + + virtual AccessCheckResult startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector& accessControlProfileIds) = 0; + + virtual optional> retrieveEntryValue( + const vector& encryptedContent, const string& nameSpace, const string& name, + const vector& accessControlProfileIds) = 0; + + virtual optional> finishRetrieval(); + + virtual optional> deleteCredential(const string& docType, + size_t proofOfDeletionCborSize) = 0; + + virtual bool shutdown() = 0; +}; + +} // namespace android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/common/WritableIdentityCredential.cpp similarity index 70% rename from identity/aidl/default/WritableIdentityCredential.cpp rename to identity/aidl/default/common/WritableIdentityCredential.cpp index 141b4deaef..1328f3629e 100644 --- a/identity/aidl/default/WritableIdentityCredential.cpp +++ b/identity/aidl/default/common/WritableIdentityCredential.cpp @@ -17,7 +17,6 @@ #define LOG_TAG "WritableIdentityCredential" #include "WritableIdentityCredential.h" -#include "IdentityCredentialStore.h" #include @@ -30,8 +29,8 @@ #include #include "IdentityCredentialStore.h" -#include "Util.h" -#include "WritableIdentityCredential.h" + +#include "FakeSecureHardwareProxy.h" namespace aidl::android::hardware::identity { @@ -40,74 +39,55 @@ using ::std::optional; using namespace ::android::hardware::identity; bool WritableIdentityCredential::initialize() { - optional> random = support::getRandom(16); - if (!random) { - LOG(ERROR) << "Error creating storageKey"; + if (!hwProxy_->initialize(testCredential_)) { + LOG(ERROR) << "hwProxy->initialize failed"; return false; } - storageKey_ = random.value(); startPersonalizationCalled_ = false; firstEntry_ = true; return true; } -// This function generates the attestation certificate using the passed in -// |attestationApplicationId| and |attestationChallenge|. It will generate an -// attestation certificate with current time and expires one year from now. The -// certificate shall contain all values as specified in hal. +WritableIdentityCredential::~WritableIdentityCredential() {} + ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( - const vector& attestationApplicationId, // - const vector& attestationChallenge, // - vector* outCertificateChain) { - if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) { + const vector& attestationApplicationId, + const vector& attestationChallenge, vector* outCertificateChain) { + if (getAttestationCertificateAlreadyCalled_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error attestation certificate previously generated")); } + getAttestationCertificateAlreadyCalled_ = true; + if (attestationChallenge.empty()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty")); } - vector challenge(attestationChallenge.begin(), attestationChallenge.end()); - vector appId(attestationApplicationId.begin(), attestationApplicationId.end()); - - optional, vector>>> keyAttestationPair = - support::createEcKeyPairAndAttestation(challenge, appId, testCredential_); - if (!keyAttestationPair) { - LOG(ERROR) << "Error creating credentialKey and attestation"; + optional> certChain = + hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId); + if (!certChain) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, - "Error creating credentialKey and attestation")); + "Error generating attestation certificate chain")); } - vector keyPair = keyAttestationPair.value().first; - certificateChain_ = keyAttestationPair.value().second; - - optional> pubKey = support::ecKeyPairGetPublicKey(keyPair); - if (!pubKey) { + optional>> certs = support::certificateChainSplit(certChain.value()); + if (!certs) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, - "Error getting public part of credentialKey")); + "Error splitting chain into separate certificates")); } - credentialPubKey_ = pubKey.value(); - optional> privKey = support::ecKeyPairGetPrivateKey(keyPair); - if (!privKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting private part of credentialKey")); - } - credentialPrivKey_ = privKey.value(); - - // convert from vector>> to vector* *outCertificateChain = vector(); - for (const vector& cert : certificateChain_) { + for (const vector& cert : certs.value()) { Certificate c = Certificate(); c.encodedCertificate = cert; outCertificateChain->push_back(std::move(c)); } + return ndk::ScopedAStatus::ok(); } @@ -123,8 +103,8 @@ ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already")); } - startPersonalizationCalled_ = true; + numAccessControlProfileRemaining_ = accessControlProfileCount; remainingEntryCounts_ = entryCounts; entryNameSpace_ = ""; @@ -133,6 +113,12 @@ ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( signedDataNamespaces_ = cppbor::Map(); signedDataCurrentNamespace_ = cppbor::Array(); + if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_, + expectedProofOfProvisioningSize_)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization")); + } + return ndk::ScopedAStatus::ok(); } @@ -140,8 +126,6 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired, int64_t timeoutMillis, int64_t secureUserId, SecureAccessControlProfile* outSecureAccessControlProfile) { - SecureAccessControlProfile profile; - if (numAccessControlProfileRemaining_ == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, @@ -169,25 +153,21 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "userAuthenticationRequired is false but timeout is non-zero")); } - // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero. - if (userAuthenticationRequired && secureUserId == 0) { + optional> mac = hwProxy_->addAccessControlProfile( + id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis, + secureUserId); + if (!mac) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_INVALID_DATA, - "userAuthenticationRequired is true but secureUserId is zero")); + IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile")); } + SecureAccessControlProfile profile; profile.id = id; profile.readerCertificate = readerCertificate; profile.userAuthenticationRequired = userAuthenticationRequired; profile.timeoutMillis = timeoutMillis; profile.secureUserId = secureUserId; - optional> mac = secureAccessControlProfileCalcMac(profile, storageKey_); - if (!mac) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile")); - } profile.mac = mac.value(); - cppbor::Map profileMap; profileMap.add("id", profile.id); if (profile.readerCertificate.encodedCertificate.size() > 0) { @@ -261,14 +241,18 @@ ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry( remainingEntryCounts_[0] -= 1; } - entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds); - entryRemainingBytes_ = entrySize; entryNameSpace_ = nameSpace; entryName_ = name; entryAccessControlProfileIds_ = accessControlProfileIds; entryBytes_.resize(0); // LOG(INFO) << "name=" << name << " entrySize=" << entrySize; + + if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry")); + } + return ndk::ScopedAStatus::ok(); } @@ -297,16 +281,11 @@ ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector> nonce = support::getRandom(12); - if (!nonce) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce")); - } - optional> encryptedContent = - support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_); + optional> encryptedContent = hwProxy_->addEntryValue( + entryAccessControlProfileIds_, entryNameSpace_, entryName_, content); if (!encryptedContent) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content")); + IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue")); } if (entryRemainingBytes_ == 0) { @@ -332,50 +311,6 @@ ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector& storageKey, - const vector& credentialPrivKey, - vector& credentialKeys) { - if (storageKey.size() != 16) { - LOG(ERROR) << "Size of storageKey is not 16"; - return false; - } - - cppbor::Array array; - array.add(cppbor::Bstr(storageKey)); - array.add(cppbor::Bstr(credentialPrivKey)); - credentialKeys = array.encode(); - return true; -} - -// Writes CBOR-encoded structure to |credentialData| containing |docType|, -// |testCredential| and |credentialKeys|. The latter element will be stored in -// encrypted form, using |hardwareBoundKey| as the encryption key. -bool generateCredentialData(const vector& hardwareBoundKey, const string& docType, - bool testCredential, const vector& credentialKeys, - vector& credentialData) { - optional> nonce = support::getRandom(12); - if (!nonce) { - LOG(ERROR) << "Error getting random"; - return false; - } - vector docTypeAsVec(docType.begin(), docType.end()); - optional> credentialBlob = support::encryptAes128Gcm( - hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec); - if (!credentialBlob) { - LOG(ERROR) << "Error encrypting CredentialKeys blob"; - return false; - } - - cppbor::Array array; - array.add(docType); - array.add(testCredential); - array.add(cppbor::Bstr(credentialBlob.value())); - credentialData = array.encode(); - return true; -} - ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( vector* outCredentialData, vector* outProofOfProvisioningSignature) { if (numAccessControlProfileRemaining_ != 0) { @@ -411,31 +346,37 @@ ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( .c_str())); } - optional> signature = support::coseSignEcDsa(credentialPrivKey_, - encodedCbor, // payload - {}, // additionalData - {}); // certificateChain + optional> signatureOfToBeSigned = hwProxy_->finishAddingEntries(); + if (!signatureOfToBeSigned) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries")); + } + + optional> signature = + support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(), + encodedCbor, // data + {}); // certificateChain if (!signature) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); } - vector credentialKeys; - if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) { + optional> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_); + if (!encryptedCredentialKeys) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys")); - } - - vector credentialData; - if (!generateCredentialData( - testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(), - docType_, testCredential_, credentialKeys, credentialData)) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData")); + IIdentityCredentialStore::STATUS_FAILED, + "Error generating encrypted CredentialKeys")); } + cppbor::Array array; + array.add(docType_); + array.add(testCredential_); + array.add(encryptedCredentialKeys.value()); + vector credentialData = array.encode(); *outCredentialData = credentialData; *outProofOfProvisioningSignature = signature.value(); + hwProxy_->shutdown(); + return ndk::ScopedAStatus::ok(); } diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/common/WritableIdentityCredential.h similarity index 85% rename from identity/aidl/default/WritableIdentityCredential.h rename to identity/aidl/default/common/WritableIdentityCredential.h index 56458520c1..c6f0628cae 100644 --- a/identity/aidl/default/WritableIdentityCredential.h +++ b/identity/aidl/default/common/WritableIdentityCredential.h @@ -23,16 +23,24 @@ #include #include +#include "IdentityCredentialStore.h" +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { +using ::android::sp; +using ::android::hardware::identity::SecureHardwareProvisioningProxy; using ::std::set; using ::std::string; using ::std::vector; class WritableIdentityCredential : public BnWritableIdentityCredential { public: - WritableIdentityCredential(const string& docType, bool testCredential) - : docType_(docType), testCredential_(testCredential) {} + WritableIdentityCredential(sp hwProxy, const string& docType, + bool testCredential) + : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {} + + ~WritableIdentityCredential(); // Creates the Credential Key. Returns false on failure. Must be called // right after construction. @@ -57,7 +65,6 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { ndk::ScopedAStatus beginAddEntry(const vector& accessControlProfileIds, const string& nameSpace, const string& name, int32_t entrySize) override; - ndk::ScopedAStatus addEntryValue(const vector& content, vector* outEncryptedContent) override; @@ -66,18 +73,17 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { vector* outProofOfProvisioningSignature) override; private: + // Set by constructor. + sp hwProxy_; string docType_; bool testCredential_; // This is set in initialize(). - vector storageKey_; bool startPersonalizationCalled_; bool firstEntry_; - // These are set in getAttestationCertificate(). - vector credentialPrivKey_; - vector credentialPubKey_; - vector> certificateChain_; + // This is set in getAttestationCertificate(). + bool getAttestationCertificateAlreadyCalled_ = false; // These fields are initialized during startPersonalization() size_t numAccessControlProfileRemaining_; @@ -92,7 +98,6 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { // These fields are initialized during beginAddEntry() size_t entryRemainingBytes_; - vector entryAdditionalData_; string entryNameSpace_; string entryName_; vector entryAccessControlProfileIds_; diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c new file mode 100644 index 0000000000..ec049b1c0d --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.c @@ -0,0 +1,236 @@ +/* + * 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 "EicCbor.h" + +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256; + eicOpsSha256Init(&cbor->digester.sha256); +} + +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256; + eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize); +} + +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Final(&cbor->digester.sha256, digest); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest); + break; + } +} + +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Update(&cbor->digester.sha256, data, size); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size); + break; + } + + if (cbor->size >= cbor->bufferSize) { + cbor->size += size; + return; + } + + size_t numBytesLeft = cbor->bufferSize - cbor->size; + size_t numBytesToCopy = size; + if (numBytesToCopy > numBytesLeft) { + numBytesToCopy = numBytesLeft; + } + eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy); + + cbor->size += size; +} + +size_t eicCborAdditionalLengthBytesFor(size_t size) { + if (size < 24) { + return 0; + } else if (size <= 0xff) { + return 1; + } else if (size <= 0xffff) { + return 2; + } else if (size <= 0xffffffff) { + return 4; + } + return 8; +} + +void eicCborBegin(EicCbor* cbor, int majorType, size_t size) { + uint8_t data[9]; + + if (size < 24) { + data[0] = (majorType << 5) | size; + eicCborAppend(cbor, data, 1); + } else if (size <= 0xff) { + data[0] = (majorType << 5) | 24; + data[1] = size; + eicCborAppend(cbor, data, 2); + } else if (size <= 0xffff) { + data[0] = (majorType << 5) | 25; + data[1] = size >> 8; + data[2] = size & 0xff; + eicCborAppend(cbor, data, 3); + } else if (size <= 0xffffffff) { + data[0] = (majorType << 5) | 26; + data[1] = (size >> 24) & 0xff; + data[2] = (size >> 16) & 0xff; + data[3] = (size >> 8) & 0xff; + data[4] = size & 0xff; + eicCborAppend(cbor, data, 5); + } else { + data[0] = (majorType << 5) | 24; + data[1] = (((uint64_t)size) >> 56) & 0xff; + data[2] = (((uint64_t)size) >> 48) & 0xff; + data[3] = (((uint64_t)size) >> 40) & 0xff; + data[4] = (((uint64_t)size) >> 32) & 0xff; + data[5] = (((uint64_t)size) >> 24) & 0xff; + data[6] = (((uint64_t)size) >> 16) & 0xff; + data[7] = (((uint64_t)size) >> 8) & 0xff; + data[8] = ((uint64_t)size) & 0xff; + eicCborAppend(cbor, data, 9); + } +} + +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize); + eicCborAppend(cbor, data, dataSize); +} + +void eicCborAppendString(EicCbor* cbor, const char* str) { + size_t length = eicStrLen(str); + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length); + eicCborAppend(cbor, (const uint8_t*)str, length); +} + +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue); +} + +void eicCborAppendBool(EicCbor* cbor, bool value) { + uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE; + eicCborAppendSimple(cbor, simpleValue); +} + +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded); +} + +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded); +} + +void eicCborAppendNumber(EicCbor* cbor, int64_t value) { + if (value < 0) { + size_t encoded = -1 - value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded); + } else { + eicCborAppendUnsigned(cbor, value); + } +} + +void eicCborAppendArray(EicCbor* cbor, size_t numElements) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements); +} + +void eicCborAppendMap(EicCbor* cbor, size_t numPairs) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs); +} + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) { + size_t numPairs = 1; + if (readerCertificateSize > 0) { + numPairs += 1; + } + if (userAuthenticationRequired) { + numPairs += 2; + if (secureUserId > 0) { + numPairs += 1; + } + } + eicCborAppendMap(cborBuilder, numPairs); + eicCborAppendString(cborBuilder, "id"); + eicCborAppendUnsigned(cborBuilder, id); + if (readerCertificateSize > 0) { + eicCborAppendString(cborBuilder, "readerCertificate"); + eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize); + } + if (userAuthenticationRequired) { + eicCborAppendString(cborBuilder, "userAuthenticationRequired"); + eicCborAppendBool(cborBuilder, userAuthenticationRequired); + eicCborAppendString(cborBuilder, "timeoutMillis"); + eicCborAppendUnsigned(cborBuilder, timeoutMillis); + if (secureUserId > 0) { + eicCborAppendString(cborBuilder, "secureUserId"); + eicCborAppendUnsigned(cborBuilder, secureUserId); + } + } + + if (cborBuilder->size > cborBuilder->bufferSize) { + eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize, + cborBuilder->size); + return false; + } + + return true; +} + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) { + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, cborBufferSize); + eicCborAppendMap(&cborBuilder, 3); + eicCborAppendString(&cborBuilder, "Namespace"); + eicCborAppendString(&cborBuilder, nameSpace); + eicCborAppendString(&cborBuilder, "Name"); + eicCborAppendString(&cborBuilder, name); + eicCborAppendString(&cborBuilder, "AccessControlProfileIds"); + eicCborAppendArray(&cborBuilder, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]); + } + if (cborBuilder.size > cborBufferSize) { + eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd", + cborBufferSize, cborBuilder.size); + return false; + } + if (outAdditionalDataCborSize != NULL) { + *outAdditionalDataCborSize = cborBuilder.size; + } + eicCborFinal(&cborBuilder, additionalDataSha256); + return true; +} diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h new file mode 100644 index 0000000000..4686b38447 --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.h @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H +#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicOps.h" + +typedef enum { + EIC_CBOR_DIGEST_TYPE_SHA256, + EIC_CBOR_DIGEST_TYPE_HMAC_SHA256, +} EicCborDigestType; + +/* EicCbor is a utility class to build CBOR data structures and calculate + * digests on the fly. + */ +typedef struct { + // Contains the size of the built CBOR, even if it exceeds bufferSize (will + // never write to buffer beyond bufferSize though) + size_t size; + + // The size of the buffer. Is zero if no data is recorded in which case + // only digesting is performed. + size_t bufferSize; + + // Whether we're producing a SHA-256 or HMAC-SHA256 digest. + EicCborDigestType digestType; + + // The SHA-256 digester object. + union { + EicSha256Ctx sha256; + EicHmacSha256Ctx hmacSha256; + } digester; + + // The buffer used for building up CBOR or NULL if bufferSize is 0. + uint8_t* buffer; +} EicCbor; + +/* Initializes an EicCbor. + * + * The given buffer will be used, up to bufferSize. + * + * If bufferSize is 0, buffer may be NULL. + */ +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize); + +/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256. + */ +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize); + +/* Finishes building CBOR and returns the digest. */ +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +/* Appends CBOR data to the EicCbor. */ +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size); + +#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0 +#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1 +#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2 +#define EIC_CBOR_MAJOR_TYPE_STRING 3 +#define EIC_CBOR_MAJOR_TYPE_ARRAY 4 +#define EIC_CBOR_MAJOR_TYPE_MAP 5 +#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6 +#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7 + +#define EIC_CBOR_SIMPLE_VALUE_FALSE 20 +#define EIC_CBOR_SIMPLE_VALUE_TRUE 21 + +#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24 + +/* Begins a new CBOR value. */ +void eicCborBegin(EicCbor* cbor, int majorType, size_t size); + +/* Appends a bytestring. */ +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize); + +/* Appends a NUL-terminated UTF-8 string. */ +void eicCborAppendString(EicCbor* cbor, const char* str); + +/* Appends a simple value. */ +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue); + +/* Appends a boolean. */ +void eicCborAppendBool(EicCbor* cbor, bool value); + +/* Appends a semantic */ +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value); + +/* Appends an unsigned number. */ +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value); + +/* Appends a number. */ +void eicCborAppendNumber(EicCbor* cbor, int64_t value); + +/* Starts appending an array. + * + * After this numElements CBOR elements must follow. + */ +void eicCborAppendArray(EicCbor* cbor, size_t numElements); + +/* Starts appending a map. + * + * After this numPairs pairs of CBOR elements must follow. + */ +void eicCborAppendMap(EicCbor* cbor, size_t numPairs); + +/* Calculates how many bytes are needed to store a size. */ +size_t eicCborAdditionalLengthBytesFor(size_t size); + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId); + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]); + +// The maximum size of an encoded Secure Access Control Profile that we +// support. Since the SACP may contain a reader certificate chain these can get +// pretty big. +// +// Currently we allocate space on the stack for this structure which is why we +// have a maximum size. We can get rid of the maximum size by incrementally +// building/verifying the SACP. TODO: actually do this. +// +#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512 + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h new file mode 100644 index 0000000000..da4dabf879 --- /dev/null +++ b/identity/aidl/default/libeic/EicOps.h @@ -0,0 +1,299 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H +#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H + +#include +#include +#include +#include + +// Uncomment or define if debug messages are needed. +// +//#define EIC_DEBUG + +#ifdef __cplusplus +extern "C" { +#endif + +// The following defines must be set to something appropriate +// +// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx +// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx +// +// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined +// as sizeof(SHA256_CTX). +// +// We expect the implementation to provide a header file with the name +// EicOpsImpl.h to do all this. +// +#include "EicOpsImpl.h" + +#define EIC_SHA256_DIGEST_SIZE 32 + +// The size of a P-256 private key. +// +#define EIC_P256_PRIV_KEY_SIZE 32 + +// The size of a P-256 public key in uncompressed form. +// +// The public key is stored in uncompressed form, first the X coordinate, then +// the Y coordinate. +// +#define EIC_P256_PUB_KEY_SIZE 64 + +// Size of one of the coordinates in a curve-point. +// +#define EIC_P256_COORDINATE_SIZE 32 + +// The size of an ECSDA signature using P-256. +// +// The R and S values are stored here, first R then S. +// +#define EIC_ECDSA_P256_SIGNATURE_SIZE 64 + +#define EIC_AES_128_KEY_SIZE 16 + +// The following are definitions of implementation functions the +// underlying platform must provide. +// + +struct EicSha256Ctx { + uint8_t reserved[EIC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicSha256Ctx EicSha256Ctx; + +struct EicHmacSha256Ctx { + uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicHmacSha256Ctx EicHmacSha256Ctx; + +#ifdef EIC_DEBUG +// Debug macro. Don't include a new-line in message. +// +#define eicDebug(...) \ + do { \ + eicPrint("%s:%d: ", __FILE__, __LINE__); \ + eicPrint(__VA_ARGS__); \ + eicPrint("\n"); \ + } while (0) +#else +#define eicDebug(...) \ + do { \ + } while (0) +#endif + +// Prints message which should include new-line character. Can be no-op. +// +// Don't use this from code, use eicDebug() instead. +// +#ifdef EIC_DEBUG +void eicPrint(const char* format, ...); +#else +inline void eicPrint(const char*, ...) {} +#endif + +// Dumps data as pretty-printed hex. Can be no-op. +// +#ifdef EIC_DEBUG +void eicHexdump(const char* message, const uint8_t* data, size_t dataSize); +#else +inline void eicHexdump(const char*, const uint8_t*, size_t) {} +#endif + +// Pretty-prints encoded CBOR. Can be no-op. +// +// If a byte-string is larger than |maxBStrSize| its contents will not be +// printed, instead the value of the form "" will be printed. Pass zero +// for |maxBStrSize| to disable this. +// +#ifdef EIC_DEBUG +void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize); +#else +inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {} +#endif + +// Memory setting, see memset(3). +void* eicMemSet(void* s, int c, size_t n); + +// Memory copying, see memcpy(3). +void* eicMemCpy(void* dest, const void* src, size_t n); + +// String length, see strlen(3). +size_t eicStrLen(const char* s); + +// Memory compare, see CRYPTO_memcmp(3SSL) +// +// It takes an amount of time dependent on len, but independent of the contents of the +// memory regions pointed to by s1 and s2. +// +int eicCryptoMemCmp(const void* s1, const void* s2, size_t n); + +// Random number generation. +bool eicOpsRandom(uint8_t* buf, size_t numBytes); + +// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes). +// +// Otherwise returns all zeroes (16 bytes). +// +const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential); + +// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|, +// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which +// must be of size |dataSize| + 28. +bool eicOpsEncryptAes128Gcm( + const uint8_t* key, // Must be 16 bytes + const uint8_t* nonce, // Must be 12 bytes + const uint8_t* data, // May be NULL if size is 0 + size_t dataSize, + const uint8_t* additionalAuthenticationData, // May be NULL if size is 0 + size_t additionalAuthenticationDataSize, uint8_t* encryptedData); + +// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|, +// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28. +// +// The format of |encryptedData| must be as specified in the +// encryptAes128Gcm() function. +bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes + const uint8_t* encryptedData, size_t encryptedDataSize, + const uint8_t* additionalAuthenticationData, + size_t additionalAuthenticationDataSize, uint8_t* data); + +// Creates an EC key using the P-256 curve. The private key is written to +// |privateKey|. The public key is written to |publicKey|. +// +bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]); + +// Generates CredentialKey plus an attestation certificate. +// +// The attestation certificate will be signed by the attestation keys the secure +// area has been provisioned with. The given |challenge| and |applicationId| +// will be used as will |testCredential|. +// +// The generated certificate will be in X.509 format and returned in |cert| +// and |certSize| must be set to the size of this array and this function will +// set it to the size of the certification chain on successfully return. +// +// This may return either a single certificate or an entire certificate +// chain. If it returns only a single certificate, the implementation of +// SecureHardwareProvisioningProxy::createCredentialKey() should amend the +// remainder of the certificate chain on the HAL side. +// +bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, bool testCredential, uint8_t* cert, + size_t* certSize); // inout + +// Generate an X.509 certificate for the key identified by |publicKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +// The certificate will be signed by the key identified by |signingKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial, + const char* issuerName, const char* subjectName, time_t validityNotBefore, + time_t validityNotAfter, uint8_t* cert, + size_t* certSize); // inout + +// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must +// be given by |digestOfData|). Returns the signature in |signature|. +// +bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE], + uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// Performs Elliptic Curve Diffie-Helman. +// +bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]); + +// Performs HKDF. +// +bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt, + size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output, + size_t outputSize); + +// SHA-256 functions. +void eicOpsSha256Init(EicSha256Ctx* ctx); +void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// HMAC SHA-256 functions. +void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize); +void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// Extracts the public key in the given X.509 certificate. +// +// If the key is not an EC key, this function fails. +// +// Otherwise the public key is stored in uncompressed form in |publicKey| which +// size should be set in |publicKeySize|. On successful return |publicKeySize| +// is set to the length of the key. If there is not enough space, the function +// fails. +// +// (The public key returned is not necessarily a P-256 key, even if it is note +// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.) +// +bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey, + size_t* publicKeySize); + +// Checks that the X.509 certificate given by |x509Cert| is signed by the public +// key given by |publicKey| which must be an EC key in uncompressed form (e.g. +// same formatt as returned by eicOpsX509GetPublicKey()). +// +bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Checks that |signature| is a signature of some data (given by |digest|), +// signed by the public key given by |publicKey|. +// +// The key must be an EC key in uncompressed form (e.g. same format as returned +// by eicOpsX509GetPublicKey()). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// The size of digest must match the size of the key. +// +bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize, + const uint8_t* signature, size_t signatureSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Validates that the passed in data constitutes a valid auth- and verification tokens. +// +bool eicOpsValidateAuthToken(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); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c new file mode 100644 index 0000000000..d3f5556f66 --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.c @@ -0,0 +1,728 @@ +/* + * 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 + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]) { + uint8_t credentialKeys[52]; + + eicMemSet(ctx, '\0', sizeof(EicPresentation)); + + if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys, + 80, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) { + eicDebug("Error decrypting CredentialKeys"); + return false; + } + + // It's supposed to look like this; + // + // CredentialKeys = [ + // bstr, ; storageKey, a 128-bit AES key + // bstr ; credentialPrivKey, the private key for credentialKey + // ] + // + // where storageKey is 16 bytes and credentialPrivateKey is 32 bytes. + // + // So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements + // and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be + // a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20. + // + if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 || + credentialKeys[19] != 0x20) { + 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; + return true; +} + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, 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]; + + 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, 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, eicStrLen(docType), 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); + eicCborAppendString(&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); + eicCborAppendString(&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) { + 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; + } + + if (timeoutMillis == 0) { + if (ctx->authTokenChallenge == 0) { + eicDebug("No challenge in authToken"); + return false; + } + + // If we didn't create a challenge, too bad but user auth with + // timeoutMillis set to 0 needs it. + if (ctx->authChallenge == 0) { + eicDebug("No challenge was created for this session"); + return false; + } + 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) { + *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 + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + 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 |= (1 << id); + if (readerCertificateSize > 0) { + ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id); + } + if (!passedReaderAuth) { + ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id); + } + if (!passedUserAuth) { + ctx->accessControlProfileMaskFailedUserAuth |= (1 << 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, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize) { + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType, + eicStrLen(docType), 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); + eicCborAppendString(&ctx->cbor, "MAC0"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time... the CBOR to be written is + // + // DeviceAuthentication = [ + // "DeviceAuthentication", + // SessionTranscript, + // DocType, ; DocType as used in Documents structure in OfflineResponse + // DeviceNameSpacesBytes + // ] + // + // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + // + // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication) + // + // which is easily calculated below + // + size_t calculatedSize = 0; + calculatedSize += 1; // Array of size 4 + calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes + calculatedSize += sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL + calculatedSize += sessionTranscriptSize; // Already CBOR encoded + size_t docTypeLen = eicStrLen(docType); + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen; + 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); + eicCborAppendString(&ctx->cbor, "DeviceAuthentication"); + eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize); + eicCborAppendString(&ctx->cbor, docType); + + // 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, const char* name, + unsigned int newNamespaceNumEntries, int32_t /* entrySize */, + const int* accessControlProfileIds, size_t numAccessControlProfileIds, + uint8_t* scratchSpace, size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + if (newNamespaceNumEntries > 0) { + eicCborAppendString(&ctx->cbor, nameSpace); + 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... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &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); + } + 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, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + 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 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); + eicCborAppendString(&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, 3); + eicCborAppendString(&cbor, "ProofOfDeletion"); + eicCborAppendString(&cbor, docType); + 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; +} diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h new file mode 100644 index 0000000000..d79896212e --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +// The maximum size we support for public keys in reader certificates. +#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65 + +typedef struct { + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + // The challenge generated with eicPresentationCreateAuthChallenge() + uint64_t authChallenge; + + // Set by eicPresentationSetAuthToken() and contains the fields + // from the passed in authToken and verificationToken. + // + uint64_t authTokenChallenge; + uint64_t authTokenSecureUserId; + uint64_t authTokenTimestamp; + uint64_t verificationTokenTimestamp; + + // The public key for the reader. + // + // (During the process of pushing reader certificates, this is also used to store + // the public key of the previously pushed certificate.) + // + uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE]; + size_t readerPublicKeySize; + + // This is set to true only if eicPresentationValidateRequestMessage() successfully + // validated the requestMessage. + // + // Why even record this? Because there's no requirement the HAL actually calls that + // function and we validate ACPs before it's called... so it's possible that a + // compromised HAL could trick us into marking ACPs as authorized while they in fact + // aren't. + bool requestMessageValidated; + bool buildCbor; + + // Set to true initialized as a test credential. + bool testCredential; + + // These are bitmasks indicating which of the possible 32 access control profiles are + // authorized. They are built up by eicPresentationValidateAccessControlProfile(). + // + uint32_t accessControlProfileMaskValidated; // True if the profile was validated. + uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using reader auth + uint32_t accessControlProfileMaskFailedReaderAuth; // True if failed reader auth + uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + size_t expectedCborSizeAtEnd; + EicCbor cbor; +} EicPresentation; + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]); + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now, + uint8_t* publicKeyCert, size_t* publicKeyCertSize, + uint8_t signingKeyBlob[60]); + +// Create an ephemeral key-pair. +// +// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in +// |ephemeralPrivateKey|. +// +bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]); + +// Returns a non-zero challenge in |authChallenge|. +bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge); + +// Starts retrieveing entries. +// +bool eicPresentationStartRetrieveEntries(EicPresentation* ctx); + +// Sets the auth-token. +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); + +// Function to push certificates in the reader certificate chain. +// +// This should start with the root certificate (e.g. the last in the chain) and +// continue up the chain, ending with the certificate for the reader. +// +// Calls to this function should be interleaved with calls to the +// eicPresentationValidateAccessControlProfile() function, see below. +// +bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509, + size_t certX509Size); + +// Checks an access control profile. +// +// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out). +// +// Returns in |accessGranted| whether access is granted. +// +// If |readerCertificate| is non-empty and the public key of one of those +// certificates appear in the chain presented by the reader, this function must +// be called after pushing that certificate using +// eicPresentationPushReaderCert(). +// +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); + +// Validates that the given requestMessage is signed by the public key in the +// certificate last set with eicPresentationPushReaderCert(). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// Must be called after eicPresentationPushReaderCert() have been used to push +// the final certificate. Which is the certificate of the reader itself. +// +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); + +typedef enum { + // Returned if access is granted. + EIC_ACCESS_CHECK_RESULT_OK, + + // Returned if an error occurred checking for access. + EIC_ACCESS_CHECK_RESULT_FAILED, + + // Returned if access was denied because item is configured without any + // access control profiles. + EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES, + + // Returned if access was denied because of user authentication. + EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED, + + // Returned if access was denied because of reader authentication. + EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED, +} EicAccessCheckResult; + +// Passes enough information to calculate the MACing key +// +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, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize); + +// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024 +// bytes, the bigger the better). It's done this way to avoid allocating stack +// space. +// +EicAccessCheckResult eicPresentationStartRetrieveEntryValue( + EicPresentation* ctx, const char* nameSpace, const char* name, + unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize); + +// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent, + size_t encryptedContentSize, uint8_t* content, + const char* nameSpace, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute +// and Verify a MAC". +bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced, + size_t* digestToBeMacedSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfDeletion CBOR. +// +bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType, + size_t proofOfDeletionCborSize, + uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c new file mode 100644 index 0000000000..f16605cfad --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.c @@ -0,0 +1,290 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EicProvisioning.h" + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) { + eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + ctx->testCredential = testCredential; + if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) { + return false; + } + + return true; +} + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize) { + if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize, + applicationId, applicationIdSize, ctx->testCredential, + publicKeyCert, publicKeyCertSize)) { + return false; + } + return true; +} + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningSize) { + if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) { + return false; + } + if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) { + return false; + } + + ctx->numEntryCounts = numEntryCounts; + if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) { + return false; + } + for (size_t n = 0; n < numEntryCounts; n++) { + if (entryCounts[n] >= 256) { + return false; + } + ctx->entryCounts[n] = entryCounts[n]; + } + ctx->curNamespace = -1; + ctx->curNamespaceNumProcessed = 0; + + eicCborInit(&ctx->cbor, NULL, 0); + + // What we're going to sign is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize); + ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size; + + eicCborAppendArray(&ctx->cbor, 5); + eicCborAppendString(&ctx->cbor, "ProofOfProvisioning"); + eicCborAppendString(&ctx->cbor, docType); + + eicCborAppendArray(&ctx->cbor, accessControlProfileCount); + + return true; +} + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]) { + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + + // Calculate and return MAC + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer, + cborBuilder.size, outMac)) { + return false; + } + + // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build + // it again. + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, + 0 /* secureUserId */)) { + return false; + } + + // Append the CBOR from the local builder to the digester. + eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size); + + return true; +} + +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + // We'll need to calc and store a digest of additionalData to check that it's the same + // additionalData being passed in for every eicProvisioningAddEntryValue() call... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + ctx->additionalDataSha256)) { + return false; + } + + if (ctx->curNamespace == -1) { + ctx->curNamespace = 0; + ctx->curNamespaceNumProcessed = 0; + // Opens the main map: { * Namespace => [ + Entry ] } + eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts); + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) { + ctx->curNamespace += 1; + ctx->curNamespaceNumProcessed = 0; + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + eicCborAppendMap(&ctx->cbor, 3); + eicCborAppendString(&ctx->cbor, "name"); + eicCborAppendString(&ctx->cbor, name); + + ctx->curEntrySize = entrySize; + ctx->curEntryNumBytesReceived = 0; + + eicCborAppendString(&ctx->cbor, "value"); + + ctx->curNamespaceNumProcessed += 1; + return true; +} + +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + eicCborAppend(&ctx->cbor, content, contentSize); + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor, + additionalDataCborSize, outEncryptedContent)) { + return false; + } + + // If done with this entry, close the map + ctx->curEntryNumBytesReceived += contentSize; + if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) { + eicCborAppendString(&ctx->cbor, "accessControlProfiles"); + eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]); + } + } + return true; +} + +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE]; + + eicCborAppendBool(&ctx->cbor, ctx->testCredential); + eicCborFinal(&ctx->cbor, cborSha256); + + // This verifies that the correct expectedProofOfProvisioningSize value was + // passed in at eicStartPersonalization() time. + if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) { + eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd); + return false; + } + + if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) { + eicDebug("Error signing proofOfProvisioning"); + return false; + } + + return true; +} + +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]) { + EicCbor cbor; + uint8_t cborBuf[52]; + + eicCborInit(&cbor, cborBuf, sizeof(cborBuf)); + eicCborAppendArray(&cbor, 2); + eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE); + eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE); + if (cbor.size > sizeof(cborBuf)) { + eicDebug("Exceeded buffer size"); + return false; + } + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + eicDebug("Error getting random"); + return false; + } + if (!eicOpsEncryptAes128Gcm( + eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) { + eicDebug("Error encrypting CredentialKeys"); + return false; + } + + return true; +} diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h new file mode 100644 index 0000000000..836d16e444 --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.h @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +#define EIC_MAX_NUM_NAMESPACES 32 +#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32 + +typedef struct { + // Set by eicCreateCredentialKey. + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + int numEntryCounts; + uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES]; + + int curNamespace; + int curNamespaceNumProcessed; + + size_t curEntrySize; + size_t curEntryNumBytesReceived; + + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + + size_t expectedCborSizeAtEnd; + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + EicCbor cbor; + + bool testCredential; +} EicProvisioning; + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential); + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize); + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningingSize); + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]); + +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The outEncryptedContent array must be contentSize + 28 bytes long. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfProvisioninging CBOR. +// +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// +// +// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType) +// where +// +// CredentialKeys = [ +// bstr, ; storageKey, a 128-bit AES key +// bstr ; credentialPrivKey, the private key for credentialKey +// ] +// +// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the +// encoded CBOR for CredentialKeys is 52 bytes and consequently +// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes. +// +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h new file mode 100644 index 0000000000..88abef808f --- /dev/null +++ b/identity/aidl/default/libeic/libeic.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H +#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce + * library users to include only this file. All public interfaces, and + * only public interfaces, must be included here. + */ +#define EIC_INSIDE_LIBEIC_H +#include "EicCbor.h" +#include "EicOps.h" +#include "EicPresentation.h" +#include "EicProvisioning.h" +#undef EIC_INSIDE_LIBEIC_H + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp index bf95df523a..c290c0827e 100644 --- a/identity/aidl/default/service.cpp +++ b/identity/aidl/default/service.cpp @@ -22,20 +22,26 @@ #include "IdentityCredentialStore.h" +#include "FakeSecureHardwareProxy.h" + +using ::android::sp; using ::android::base::InitLogging; using ::android::base::StderrLogger; -using aidl::android::hardware::identity::IdentityCredentialStore; +using ::aidl::android::hardware::identity::IdentityCredentialStore; +using ::android::hardware::identity::FakeSecureHardwareProxyFactory; +using ::android::hardware::identity::SecureHardwareProxyFactory; int main(int /*argc*/, char* argv[]) { InitLogging(argv, StderrLogger); + sp hwProxyFactory = new FakeSecureHardwareProxyFactory(); + ABinderProcess_setThreadPoolMaxThreadCount(0); std::shared_ptr store = - ndk::SharedRefBase::make(); + ndk::SharedRefBase::make(hwProxyFactory); const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default"; - LOG(INFO) << "instance: " << instance; binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str()); CHECK(status == STATUS_OK); diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp index 56e17bac77..1629a0c952 100644 --- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp +++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp @@ -182,7 +182,7 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) { false /* testCredential */)); // Verify set a large number of profile count and entry count is ok - const vector entryCounts = {3000}; + const vector entryCounts = {255}; writableCredential->setExpectedProofOfProvisioningSize(123456); result = writableCredential->startPersonalization(25, entryCounts); EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()