mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 05:49:27 +00:00
Merge "Identity Credential: Switch default implementation to use libeic." am: 8f4cc703f9 am: c0d021c5da
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1245367 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I73e0f588b558a15a2992e29245b0908f086f0a31
This commit is contained in:
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
506
identity/aidl/default/EicOpsImpl.cc
Normal file
506
identity/aidl/default/EicOpsImpl.cc
Normal file
@@ -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 <optional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hkdf.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/objects.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#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<vector<uint8_t>> 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<uint8_t> cppKey;
|
||||
cppKey.resize(16);
|
||||
memcpy(cppKey.data(), key, 16);
|
||||
|
||||
vector<uint8_t> cppData;
|
||||
cppData.resize(dataSize);
|
||||
if (dataSize > 0) {
|
||||
memcpy(cppData.data(), data, dataSize);
|
||||
}
|
||||
|
||||
vector<uint8_t> cppAAD;
|
||||
cppAAD.resize(additionalAuthenticationDataSize);
|
||||
if (additionalAuthenticationDataSize > 0) {
|
||||
memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
|
||||
}
|
||||
|
||||
vector<uint8_t> cppNonce;
|
||||
cppNonce.resize(12);
|
||||
memcpy(cppNonce.data(), nonce, 12);
|
||||
|
||||
optional<vector<uint8_t>> 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<uint8_t> keyVec;
|
||||
keyVec.resize(16);
|
||||
memcpy(keyVec.data(), key, 16);
|
||||
|
||||
vector<uint8_t> encryptedDataVec;
|
||||
encryptedDataVec.resize(encryptedDataSize);
|
||||
if (encryptedDataSize > 0) {
|
||||
memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
|
||||
}
|
||||
|
||||
vector<uint8_t> aadVec;
|
||||
aadVec.resize(additionalAuthenticationDataSize);
|
||||
if (additionalAuthenticationDataSize > 0) {
|
||||
memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> 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<vector<uint8_t>> keyPair = android::hardware::identity::support::createEcKeyPair();
|
||||
if (!keyPair) {
|
||||
eicDebug("Error creating EC keypair");
|
||||
return false;
|
||||
}
|
||||
optional<vector<uint8_t>> 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<vector<uint8_t>> 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<uint8_t> challengeVec(challengeSize);
|
||||
memcpy(challengeVec.data(), challenge, challengeSize);
|
||||
|
||||
vector<uint8_t> applicationIdVec(applicationIdSize);
|
||||
memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
|
||||
|
||||
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
|
||||
android::hardware::identity::support::createEcKeyPairAndAttestation(
|
||||
challengeVec, applicationIdVec, testCredential);
|
||||
if (!ret) {
|
||||
eicDebug("Error generating CredentialKey and attestation");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract certificate chain.
|
||||
vector<uint8_t> 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<vector<uint8_t>> 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<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
|
||||
memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
|
||||
|
||||
vector<uint8_t> 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<vector<uint8_t>> 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<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
|
||||
memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
|
||||
|
||||
vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
|
||||
memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
|
||||
|
||||
optional<vector<uint8_t>> 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<uint8_t> chain;
|
||||
chain.resize(x509CertSize);
|
||||
memcpy(chain.data(), x509Cert, x509CertSize);
|
||||
optional<vector<uint8_t>> 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<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
|
||||
vector<uint8_t> 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<uint8_t> digestVec(digest, digest + digestSize);
|
||||
vector<uint8_t> signatureVec(signature, signature + signatureSize);
|
||||
vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
|
||||
|
||||
vector<uint8_t> 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<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
|
||||
pubKeyVec[0] = 0x04;
|
||||
memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
|
||||
|
||||
vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
|
||||
memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
|
||||
|
||||
optional<vector<uint8_t>> 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<uint8_t> sharedSecretVec(sharedSecretSize);
|
||||
memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
|
||||
vector<uint8_t> saltVec(saltSize);
|
||||
memcpy(saltVec.data(), salt, saltSize);
|
||||
vector<uint8_t> infoVec(infoSize);
|
||||
memcpy(infoVec.data(), info, infoSize);
|
||||
|
||||
optional<vector<uint8_t>> 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<uint8_t> 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<uint8_t> 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
|
||||
46
identity/aidl/default/EicOpsImpl.h
Normal file
46
identity/aidl/default/EicOpsImpl.h
Normal file
@@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Add whatever includes are needed for definitions below.
|
||||
//
|
||||
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#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
|
||||
324
identity/aidl/default/FakeSecureHardwareProxy.cpp
Normal file
324
identity/aidl/default/FakeSecureHardwareProxy.cpp
Normal file
@@ -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 <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hkdf.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/objects.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#include <libeic.h>
|
||||
|
||||
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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
|
||||
const vector<uint8_t>& challenge, const vector<uint8_t>& 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<uint8_t> pubKeyCert(publicKeyCertSize);
|
||||
memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
|
||||
return pubKeyCert;
|
||||
}
|
||||
|
||||
bool FakeSecureHardwareProvisioningProxy::startPersonalization(
|
||||
int accessControlProfileCount, vector<int> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile(
|
||||
int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
|
||||
uint64_t timeoutMillis, uint64_t secureUserId) {
|
||||
vector<uint8_t> mac(28);
|
||||
if (!eicProvisioningAddAccessControlProfile(
|
||||
&ctx_, id, readerCertificate.data(), readerCertificate.size(),
|
||||
userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) {
|
||||
return {};
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue(
|
||||
const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name,
|
||||
const vector<uint8_t>& content) {
|
||||
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() {
|
||||
vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
|
||||
if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) {
|
||||
return {};
|
||||
}
|
||||
return signatureOfToBeSigned;
|
||||
}
|
||||
|
||||
// Returns encryptedCredentialKeys (80 bytes).
|
||||
optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
|
||||
const string& docType) {
|
||||
vector<uint8_t> 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<uint8_t> 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<pair<vector<uint8_t>, vector<uint8_t>>>
|
||||
FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) {
|
||||
uint8_t publicKeyCert[512];
|
||||
size_t publicKeyCertSize = sizeof(publicKeyCert);
|
||||
vector<uint8_t> signingKeyBlob(60);
|
||||
|
||||
if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert,
|
||||
&publicKeyCertSize, signingKeyBlob.data())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<uint8_t> cert;
|
||||
cert.resize(publicKeyCertSize);
|
||||
memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
|
||||
|
||||
return std::make_pair(cert, signingKeyBlob);
|
||||
}
|
||||
|
||||
// Returns private key
|
||||
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() {
|
||||
vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
|
||||
if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
|
||||
return {};
|
||||
}
|
||||
return priv;
|
||||
}
|
||||
|
||||
optional<uint64_t> 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<uint8_t>& certX509) {
|
||||
return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
|
||||
}
|
||||
|
||||
bool FakeSecureHardwarePresentationProxy::validateRequestMessage(
|
||||
const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage,
|
||||
int coseSignAlg, const vector<uint8_t>& 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<uint8_t>& mac,
|
||||
uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
|
||||
int verificationTokenSecurityLevel, const vector<uint8_t>& verificationTokenMac) {
|
||||
return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId,
|
||||
hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
|
||||
verificationTokenChallenge, verificationTokenTimestamp,
|
||||
verificationTokenSecurityLevel, verificationTokenMac.data(),
|
||||
verificationTokenMac.size());
|
||||
}
|
||||
|
||||
optional<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile(
|
||||
int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
|
||||
int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& 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<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& 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<int32_t>& 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<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue(
|
||||
const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
|
||||
const vector<int32_t>& accessControlProfileIds) {
|
||||
uint8_t scratchSpace[512];
|
||||
vector<uint8_t> 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<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
|
||||
vector<uint8_t> mac(32);
|
||||
size_t macSize = 32;
|
||||
if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
|
||||
return {};
|
||||
}
|
||||
mac.resize(macSize);
|
||||
return mac;
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
|
||||
const string& docType, size_t proofOfDeletionCborSize) {
|
||||
vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
|
||||
if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize,
|
||||
signatureOfToBeSigned.data())) {
|
||||
return {};
|
||||
}
|
||||
return signatureOfToBeSigned;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::identity
|
||||
151
identity/aidl/default/FakeSecureHardwareProxy.h
Normal file
151
identity/aidl/default/FakeSecureHardwareProxy.h
Normal file
@@ -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 <libeic/libeic.h>
|
||||
|
||||
#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<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
|
||||
const vector<uint8_t>& applicationId) override;
|
||||
|
||||
bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
|
||||
const string& docType,
|
||||
size_t expectedProofOfProvisioningSize) override;
|
||||
|
||||
// Returns MAC (28 bytes).
|
||||
optional<vector<uint8_t>> addAccessControlProfile(int id,
|
||||
const vector<uint8_t>& readerCertificate,
|
||||
bool userAuthenticationRequired,
|
||||
uint64_t timeoutMillis,
|
||||
uint64_t secureUserId) override;
|
||||
|
||||
bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
|
||||
const string& name, uint64_t entrySize) override;
|
||||
|
||||
// Returns encryptedContent.
|
||||
optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
|
||||
const string& nameSpace, const string& name,
|
||||
const vector<uint8_t>& content) override;
|
||||
|
||||
// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
|
||||
optional<vector<uint8_t>> finishAddingEntries() override;
|
||||
|
||||
// Returns encryptedCredentialKeys (80 bytes).
|
||||
optional<vector<uint8_t>> 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<uint8_t> encryptedCredentialKeys) override;
|
||||
|
||||
// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
|
||||
optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
|
||||
time_t now) override;
|
||||
|
||||
// Returns private key
|
||||
optional<vector<uint8_t>> createEphemeralKeyPair() override;
|
||||
|
||||
optional<uint64_t> createAuthChallenge() override;
|
||||
|
||||
bool startRetrieveEntries() override;
|
||||
|
||||
bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
|
||||
int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
|
||||
uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
|
||||
int verificationTokenSecurityLevel,
|
||||
const vector<uint8_t>& verificationTokenMac) override;
|
||||
|
||||
bool pushReaderCert(const vector<uint8_t>& certX509) override;
|
||||
|
||||
optional<bool> validateAccessControlProfile(int id, const vector<uint8_t>& readerCertificate,
|
||||
bool userAuthenticationRequired, int timeoutMillis,
|
||||
uint64_t secureUserId,
|
||||
const vector<uint8_t>& mac) override;
|
||||
|
||||
bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||
const vector<uint8_t>& readerSignatureOfToBeSigned) override;
|
||||
|
||||
bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& 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<int32_t>& accessControlProfileIds) override;
|
||||
|
||||
optional<vector<uint8_t>> retrieveEntryValue(
|
||||
const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
|
||||
const vector<int32_t>& accessControlProfileIds) override;
|
||||
|
||||
optional<vector<uint8_t>> finishRetrieval() override;
|
||||
|
||||
optional<vector<uint8_t>> 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<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
|
||||
return new FakeSecureHardwareProvisioningProxy();
|
||||
}
|
||||
|
||||
sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
|
||||
return new FakeSecureHardwarePresentationProxy();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android::hardware::identity
|
||||
|
||||
#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
|
||||
@@ -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 <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
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<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
||||
const vector<uint8_t>& getHardwareBoundKey() {
|
||||
return hardwareBoundKey;
|
||||
}
|
||||
|
||||
vector<uint8_t> 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<vector<uint8_t>> secureAccessControlProfileCalcMac(
|
||||
const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
|
||||
vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
|
||||
|
||||
optional<vector<uint8_t>> nonce = support::getRandom(12);
|
||||
if (!nonce) {
|
||||
return {};
|
||||
}
|
||||
optional<vector<uint8_t>> macO =
|
||||
support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
|
||||
if (!macO) {
|
||||
return {};
|
||||
}
|
||||
return macO.value();
|
||||
}
|
||||
|
||||
bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
|
||||
const vector<uint8_t>& storageKey) {
|
||||
vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
|
||||
|
||||
if (profile.mac.size() < support::kAesGcmIvSize) {
|
||||
return false;
|
||||
}
|
||||
vector<uint8_t> nonce =
|
||||
vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize);
|
||||
optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData);
|
||||
if (!mac) {
|
||||
return false;
|
||||
}
|
||||
if (mac.value() != profile.mac) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
|
||||
const vector<int32_t> 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
|
||||
@@ -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 <aidl/android/hardware/identity/BnIdentityCredential.h>
|
||||
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cppbor/cppbor.h>
|
||||
|
||||
namespace aidl::android::hardware::identity {
|
||||
|
||||
using ::std::optional;
|
||||
using ::std::string;
|
||||
using ::std::vector;
|
||||
|
||||
// Returns the hardware-bound AES-128 key.
|
||||
const vector<uint8_t>& getHardwareBoundKey();
|
||||
|
||||
// Calculates the MAC for |profile| using |storageKey|.
|
||||
optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
|
||||
const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
|
||||
|
||||
// Checks authenticity of the MAC in |profile| using |storageKey|.
|
||||
bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
|
||||
const vector<uint8_t>& storageKey);
|
||||
|
||||
// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
|
||||
vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
|
||||
const vector<int32_t> accessControlProfileIds);
|
||||
|
||||
} // namespace aidl::android::hardware::identity
|
||||
|
||||
#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "IdentityCredential.h"
|
||||
#include "IdentityCredentialStore.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
@@ -30,6 +29,8 @@
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#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<uint8_t> hardwareBoundKey;
|
||||
if (testCredential_) {
|
||||
hardwareBoundKey = support::getTestHardwareBoundKey();
|
||||
} else {
|
||||
hardwareBoundKey = getHardwareBoundKey();
|
||||
}
|
||||
|
||||
const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
|
||||
const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
|
||||
optional<vector<uint8_t>> 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<uint8_t>* outProofOfDeletionSignature) {
|
||||
cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
|
||||
vector<uint8_t> proofOfDeletion = array.encode();
|
||||
vector<uint8_t> proofOfDeletionCbor = array.encode();
|
||||
vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
|
||||
|
||||
optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
|
||||
proofOfDeletion, // payload
|
||||
{}, // additionalData
|
||||
{}); // certificateChain
|
||||
optional<vector<uint8_t>> signatureOfToBeSigned =
|
||||
hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size());
|
||||
if (!signatureOfToBeSigned) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> 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<uint8_t>* outKeyPair) {
|
||||
optional<vector<uint8_t>> kp = support::createEcKeyPair();
|
||||
if (!kp) {
|
||||
optional<vector<uint8_t>> 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<vector<uint8_t>> 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<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
|
||||
optional<vector<uint8_t>> 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<vector<uint8_t>> 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<uint64_t> 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<uint8_t>& readerCertificateChain) {
|
||||
optional<vector<uint8_t>> acpPubKey =
|
||||
support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
|
||||
if (!acpPubKey) {
|
||||
LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
|
||||
return false;
|
||||
}
|
||||
|
||||
optional<vector<vector<uint8_t>>> certificatesInChain =
|
||||
support::certificateChainSplit(readerCertificateChain);
|
||||
if (!certificatesInChain) {
|
||||
LOG(ERROR) << "Error splitting readerCertificateChain";
|
||||
return false;
|
||||
}
|
||||
for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
|
||||
optional<vector<uint8_t>> 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<RequestNamespace>& 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<vector<uint8_t>> 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<SecureAccessControlProfile> 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<vector<uint8_t>> 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<vector<vector<uint8_t>>> 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<uint8_t>& 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<vector<uint8_t>> 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<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
|
||||
while (it != remainingAcps.end()) {
|
||||
const SecureAccessControlProfile& profile = *it;
|
||||
if (profile.readerCertificate.encodedCertificate.size() == 0) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
optional<vector<uint8_t>> 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<bool> 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<vector<uint8_t>> 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<uint8_t>& itemsRequestBytes = itemsRequest;
|
||||
vector<uint8_t> encodedReaderAuthentication =
|
||||
cppbor::Array()
|
||||
.add("ReaderAuthentication")
|
||||
.add(std::move(sessionTranscriptItem))
|
||||
.add(cppbor::Semantic(24, itemsRequestBytes))
|
||||
.encode();
|
||||
vector<uint8_t> 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<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
|
||||
if (!tbsSignature) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
|
||||
"Error extracting toBeSigned from COSE_Sign1"));
|
||||
}
|
||||
optional<int> 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<bool> 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<uint8_t> 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<unsigned int> numEntriesPerNamespace;
|
||||
for (const RequestNamespace& rns : requestNamespaces_) {
|
||||
vector<RequestDataItem> 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<uint8_t>& encryptedContent,
|
||||
vector<uint8_t>* outContent) {
|
||||
optional<vector<uint8_t>> content =
|
||||
support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
|
||||
optional<vector<uint8_t>> 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<uint8_t>* outMac,
|
||||
optional<vector<uint8_t>> mac;
|
||||
if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
|
||||
readerPublicKey_.size() > 0) {
|
||||
vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
|
||||
optional<vector<uint8_t>> signingKey =
|
||||
support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
|
||||
if (!signingKey) {
|
||||
optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
|
||||
if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_INVALID_DATA,
|
||||
"Error decrypting signingKeyBlob"));
|
||||
}
|
||||
|
||||
vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
|
||||
optional<vector<uint8_t>> 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<uint8_t>({}));
|
||||
@@ -808,56 +818,18 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
|
||||
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
|
||||
vector<uint8_t>* 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<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
|
||||
if (!signingKeyPKCS8) {
|
||||
time_t now = time(NULL);
|
||||
optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
|
||||
hwProxy_->generateSigningKeyPair(docType_, now);
|
||||
if (!pair) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> signingPublicKey =
|
||||
support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
|
||||
if (!signingPublicKey) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error getting public part of signingKey"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
|
||||
if (!signingKey) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error getting private part of signingKey"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> 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<vector<uint8_t>> nonce = support::getRandom(12);
|
||||
if (!nonce) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
|
||||
}
|
||||
vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
|
||||
optional<vector<uint8_t>> 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();
|
||||
}
|
||||
|
||||
@@ -29,10 +29,15 @@
|
||||
|
||||
#include <cppbor/cppbor.h>
|
||||
|
||||
#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<uint8_t>& credentialData)
|
||||
: credentialData_(credentialData),
|
||||
IdentityCredential(sp<SecureHardwarePresentationProxy> hwProxy,
|
||||
const vector<uint8_t>& 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<SecureHardwarePresentationProxy> hwProxy_;
|
||||
vector<uint8_t> credentialData_;
|
||||
int numStartRetrievalCalls_;
|
||||
|
||||
// Set by initialize()
|
||||
string docType_;
|
||||
bool testCredential_;
|
||||
vector<uint8_t> storageKey_;
|
||||
vector<uint8_t> credentialPrivKey_;
|
||||
|
||||
// Set by createEphemeralKeyPair()
|
||||
vector<uint8_t> ephemeralPublicKey_;
|
||||
@@ -90,9 +95,6 @@ class IdentityCredential : public BnIdentityCredential {
|
||||
// Set by setReaderEphemeralPublicKey()
|
||||
vector<uint8_t> readerPublicKey_;
|
||||
|
||||
// Set by createAuthChallenge()
|
||||
uint64_t authChallenge_;
|
||||
|
||||
// Set by setRequestedNamespaces()
|
||||
vector<RequestNamespace> requestNamespaces_;
|
||||
|
||||
@@ -100,7 +102,6 @@ class IdentityCredential : public BnIdentityCredential {
|
||||
VerificationToken verificationToken_;
|
||||
|
||||
// Set at startRetrieval() time.
|
||||
map<int32_t, int> profileIdToAccessCheckResult_;
|
||||
vector<uint8_t> signingKeyBlob_;
|
||||
vector<uint8_t> sessionTranscript_;
|
||||
vector<uint8_t> itemsRequest_;
|
||||
@@ -111,15 +112,16 @@ class IdentityCredential : public BnIdentityCredential {
|
||||
|
||||
// Calculated at startRetrieval() time.
|
||||
size_t expectedDeviceNameSpacesSize_;
|
||||
vector<unsigned int> expectedNumEntriesPerNamespace_;
|
||||
|
||||
// Set at startRetrieveEntryValue() time.
|
||||
string currentNameSpace_;
|
||||
string currentName_;
|
||||
vector<int32_t> currentAccessControlProfileIds_;
|
||||
size_t entryRemainingBytes_;
|
||||
vector<uint8_t> entryValue_;
|
||||
vector<uint8_t> entryAdditionalData_;
|
||||
|
||||
size_t calcDeviceNameSpacesSize();
|
||||
void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
|
||||
};
|
||||
|
||||
} // namespace aidl::android::hardware::identity
|
||||
@@ -39,8 +39,9 @@ ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation(
|
||||
ndk::ScopedAStatus IdentityCredentialStore::createCredential(
|
||||
const string& docType, bool testCredential,
|
||||
shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
|
||||
sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
|
||||
shared_ptr<WritableIdentityCredential> wc =
|
||||
ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential);
|
||||
ndk::SharedRefBase::make<WritableIdentityCredential>(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<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
|
||||
shared_ptr<IdentityCredential> credential =
|
||||
ndk::SharedRefBase::make<IdentityCredential>(credentialData);
|
||||
ndk::SharedRefBase::make<IdentityCredential>(hwProxy, credentialData);
|
||||
auto ret = credential->initialize();
|
||||
if (ret != IIdentityCredentialStore::STATUS_OK) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
@@ -19,15 +19,20 @@
|
||||
|
||||
#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
|
||||
|
||||
#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<SecureHardwareProxyFactory> 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<uint8_t>& credentialData,
|
||||
shared_ptr<IIdentityCredential>* outCredential) override;
|
||||
|
||||
private:
|
||||
sp<SecureHardwareProxyFactory> hwProxyFactory_;
|
||||
};
|
||||
|
||||
} // namespace aidl::android::hardware::identity
|
||||
174
identity/aidl/default/common/SecureHardwareProxy.h
Normal file
174
identity/aidl/default/common/SecureHardwareProxy.h
Normal file
@@ -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 <utils/RefBase.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
|
||||
virtual sp<SecureHardwarePresentationProxy> 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<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
|
||||
const vector<uint8_t>& applicationId) = 0;
|
||||
|
||||
virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
|
||||
const string& docType,
|
||||
size_t expectedProofOfProvisioningSize) = 0;
|
||||
|
||||
// Returns MAC (28 bytes).
|
||||
virtual optional<vector<uint8_t>> addAccessControlProfile(
|
||||
int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
|
||||
uint64_t timeoutMillis, uint64_t secureUserId) = 0;
|
||||
|
||||
virtual bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
|
||||
const string& name, uint64_t entrySize) = 0;
|
||||
|
||||
// Returns encryptedContent.
|
||||
virtual optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
|
||||
const string& nameSpace, const string& name,
|
||||
const vector<uint8_t>& content) = 0;
|
||||
|
||||
// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
|
||||
virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
|
||||
|
||||
// Returns encryptedCredentialKeys (80 bytes).
|
||||
virtual optional<vector<uint8_t>> 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<uint8_t> encryptedCredentialKeys) = 0;
|
||||
|
||||
// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
|
||||
virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
|
||||
time_t now) = 0;
|
||||
|
||||
// Returns private key
|
||||
virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
|
||||
|
||||
virtual optional<uint64_t> 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<uint8_t>& mac, uint64_t verificationTokenChallenge,
|
||||
uint64_t verificationTokenTimestamp,
|
||||
int verificationTokenSecurityLevel,
|
||||
const vector<uint8_t>& verificationTokenMac) = 0;
|
||||
|
||||
virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
|
||||
|
||||
virtual optional<bool> validateAccessControlProfile(int id,
|
||||
const vector<uint8_t>& readerCertificate,
|
||||
bool userAuthenticationRequired,
|
||||
int timeoutMillis, uint64_t secureUserId,
|
||||
const vector<uint8_t>& mac) = 0;
|
||||
|
||||
virtual bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||
const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
|
||||
|
||||
virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& 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<int32_t>& accessControlProfileIds) = 0;
|
||||
|
||||
virtual optional<vector<uint8_t>> retrieveEntryValue(
|
||||
const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
|
||||
const vector<int32_t>& accessControlProfileIds) = 0;
|
||||
|
||||
virtual optional<vector<uint8_t>> finishRetrieval();
|
||||
|
||||
virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
|
||||
size_t proofOfDeletionCborSize) = 0;
|
||||
|
||||
virtual bool shutdown() = 0;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::identity
|
||||
|
||||
#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
|
||||
@@ -17,7 +17,6 @@
|
||||
#define LOG_TAG "WritableIdentityCredential"
|
||||
|
||||
#include "WritableIdentityCredential.h"
|
||||
#include "IdentityCredentialStore.h"
|
||||
|
||||
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
|
||||
@@ -30,8 +29,8 @@
|
||||
#include <utility>
|
||||
|
||||
#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<vector<uint8_t>> 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<uint8_t>& attestationApplicationId, //
|
||||
const vector<uint8_t>& attestationChallenge, //
|
||||
vector<Certificate>* outCertificateChain) {
|
||||
if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
|
||||
const vector<uint8_t>& attestationApplicationId,
|
||||
const vector<uint8_t>& attestationChallenge, vector<Certificate>* 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<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
|
||||
vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
|
||||
|
||||
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
|
||||
support::createEcKeyPairAndAttestation(challenge, appId, testCredential_);
|
||||
if (!keyAttestationPair) {
|
||||
LOG(ERROR) << "Error creating credentialKey and attestation";
|
||||
optional<vector<uint8_t>> 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<uint8_t> keyPair = keyAttestationPair.value().first;
|
||||
certificateChain_ = keyAttestationPair.value().second;
|
||||
|
||||
optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
|
||||
if (!pubKey) {
|
||||
optional<vector<vector<uint8_t>>> 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<vector<uint8_t>> 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<vector<uint8_t>>> to vector<Certificate>*
|
||||
*outCertificateChain = vector<Certificate>();
|
||||
for (const vector<uint8_t>& cert : certificateChain_) {
|
||||
for (const vector<uint8_t>& 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<vector<uint8_t>> 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<vector<uint8_t>> 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<uint8_
|
||||
}
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> nonce = support::getRandom(12);
|
||||
if (!nonce) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
|
||||
}
|
||||
optional<vector<uint8_t>> encryptedContent =
|
||||
support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
|
||||
optional<vector<uint8_t>> 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<uint8_
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
|
||||
// |credentialPrivKey|.
|
||||
static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
|
||||
const vector<uint8_t>& credentialPrivKey,
|
||||
vector<uint8_t>& 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<uint8_t>& hardwareBoundKey, const string& docType,
|
||||
bool testCredential, const vector<uint8_t>& credentialKeys,
|
||||
vector<uint8_t>& credentialData) {
|
||||
optional<vector<uint8_t>> nonce = support::getRandom(12);
|
||||
if (!nonce) {
|
||||
LOG(ERROR) << "Error getting random";
|
||||
return false;
|
||||
}
|
||||
vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
|
||||
optional<vector<uint8_t>> 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<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
|
||||
if (numAccessControlProfileRemaining_ != 0) {
|
||||
@@ -411,31 +346,37 @@ ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
|
||||
.c_str()));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
|
||||
encodedCbor, // payload
|
||||
{}, // additionalData
|
||||
{}); // certificateChain
|
||||
optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries();
|
||||
if (!signatureOfToBeSigned) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> signature =
|
||||
support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
|
||||
encodedCbor, // data
|
||||
{}); // certificateChain
|
||||
if (!signature) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
|
||||
}
|
||||
|
||||
vector<uint8_t> credentialKeys;
|
||||
if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
|
||||
optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_);
|
||||
if (!encryptedCredentialKeys) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
|
||||
}
|
||||
|
||||
vector<uint8_t> 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<uint8_t> credentialData = array.encode();
|
||||
|
||||
*outCredentialData = credentialData;
|
||||
*outProofOfProvisioningSignature = signature.value();
|
||||
hwProxy_->shutdown();
|
||||
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
@@ -23,16 +23,24 @@
|
||||
#include <cppbor.h>
|
||||
#include <set>
|
||||
|
||||
#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<SecureHardwareProvisioningProxy> 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<int32_t>& accessControlProfileIds,
|
||||
const string& nameSpace, const string& name,
|
||||
int32_t entrySize) override;
|
||||
|
||||
ndk::ScopedAStatus addEntryValue(const vector<uint8_t>& content,
|
||||
vector<uint8_t>* outEncryptedContent) override;
|
||||
|
||||
@@ -66,18 +73,17 @@ class WritableIdentityCredential : public BnWritableIdentityCredential {
|
||||
vector<uint8_t>* outProofOfProvisioningSignature) override;
|
||||
|
||||
private:
|
||||
// Set by constructor.
|
||||
sp<SecureHardwareProvisioningProxy> hwProxy_;
|
||||
string docType_;
|
||||
bool testCredential_;
|
||||
|
||||
// This is set in initialize().
|
||||
vector<uint8_t> storageKey_;
|
||||
bool startPersonalizationCalled_;
|
||||
bool firstEntry_;
|
||||
|
||||
// These are set in getAttestationCertificate().
|
||||
vector<uint8_t> credentialPrivKey_;
|
||||
vector<uint8_t> credentialPubKey_;
|
||||
vector<vector<uint8_t>> 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<uint8_t> entryAdditionalData_;
|
||||
string entryNameSpace_;
|
||||
string entryName_;
|
||||
vector<int32_t> entryAccessControlProfileIds_;
|
||||
236
identity/aidl/default/libeic/EicCbor.c
Normal file
236
identity/aidl/default/libeic/EicCbor.c
Normal file
@@ -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;
|
||||
}
|
||||
156
identity/aidl/default/libeic/EicCbor.h
Normal file
156
identity/aidl/default/libeic/EicCbor.h
Normal file
@@ -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
|
||||
299
identity/aidl/default/libeic/EicOps.h
Normal file
299
identity/aidl/default/libeic/EicOps.h
Normal file
@@ -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 <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// 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 "<bstr size=1099016
|
||||
// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" 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
|
||||
728
identity/aidl/default/libeic/EicPresentation.c
Normal file
728
identity/aidl/default/libeic/EicPresentation.c
Normal file
@@ -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 <inttypes.h>
|
||||
|
||||
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;
|
||||
}
|
||||
229
identity/aidl/default/libeic/EicPresentation.h
Normal file
229
identity/aidl/default/libeic/EicPresentation.h
Normal file
@@ -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
|
||||
290
identity/aidl/default/libeic/EicProvisioning.c
Normal file
290
identity/aidl/default/libeic/EicProvisioning.c
Normal file
@@ -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;
|
||||
}
|
||||
123
identity/aidl/default/libeic/EicProvisioning.h
Normal file
123
identity/aidl/default/libeic/EicProvisioning.h
Normal file
@@ -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
|
||||
39
identity/aidl/default/libeic/libeic.h
Normal file
39
identity/aidl/default/libeic/libeic.h
Normal file
@@ -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
|
||||
@@ -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<SecureHardwareProxyFactory> hwProxyFactory = new FakeSecureHardwareProxyFactory();
|
||||
|
||||
ABinderProcess_setThreadPoolMaxThreadCount(0);
|
||||
std::shared_ptr<IdentityCredentialStore> store =
|
||||
ndk::SharedRefBase::make<IdentityCredentialStore>();
|
||||
ndk::SharedRefBase::make<IdentityCredentialStore>(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);
|
||||
|
||||
|
||||
@@ -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<int32_t> entryCounts = {3000};
|
||||
const vector<int32_t> entryCounts = {255};
|
||||
writableCredential->setExpectedProofOfProvisioningSize(123456);
|
||||
result = writableCredential->startPersonalization(25, entryCounts);
|
||||
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
|
||||
|
||||
Reference in New Issue
Block a user