Add attestation certificate generation and identity credential tags.
Bug: 149908474
Test: atest android.security.identity.cts.AttestationTest
Test: atest VtsHalIdentityCredentialTargetTest
Test: atest android.hardware.identity-support-lib-test
Change-Id: I18c5d05d806d4157c9dce42a398cc89421e26907
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
index ba2062d..89f7f35 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -16,6 +16,9 @@
#define LOG_TAG "WritableIdentityCredential"
+#include "WritableIdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <android-base/logging.h>
@@ -23,6 +26,8 @@
#include <cppbor/cppbor.h>
#include <cppbor/cppbor_parse.h>
+#include <utility>
+
#include "IdentityCredentialStore.h"
#include "Util.h"
#include "WritableIdentityCredential.h"
@@ -33,26 +38,6 @@
using namespace ::android::hardware::identity;
bool WritableIdentityCredential::initialize() {
- optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
- if (!keyPair) {
- LOG(ERROR) << "Error creating credentialKey";
- return false;
- }
-
- optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
- if (!pubKey) {
- LOG(ERROR) << "Error getting public part of credentialKey";
- return false;
- }
- credentialPubKey_ = pubKey.value();
-
- optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
- if (!privKey) {
- LOG(ERROR) << "Error getting private part of credentialKey";
- return false;
- }
- credentialPrivKey_ = privKey.value();
-
optional<vector<uint8_t>> random = support::getRandom(16);
if (!random) {
LOG(ERROR) << "Error creating storageKey";
@@ -63,88 +48,54 @@
return true;
}
-// TODO: use |attestationApplicationId| and |attestationChallenge| and also
-// ensure the returned certificate chain satisfy the requirements listed in
-// the docs for IWritableIdentityCredential::getAttestationCertificate()
-//
+// 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.
ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
- const vector<int8_t>& /*attestationApplicationId*/,
- const vector<int8_t>& /*attestationChallenge*/, vector<Certificate>* outCertificateChain) {
- // For now, we dynamically generate an attestion key on each and every
- // request and use that to sign CredentialKey. In a real implementation this
- // would look very differently.
- optional<vector<uint8_t>> attestationKeyPair = support::createEcKeyPair();
- if (!attestationKeyPair) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating attestationKey"));
- }
-
- optional<vector<uint8_t>> attestationPubKey =
- support::ecKeyPairGetPublicKey(attestationKeyPair.value());
- if (!attestationPubKey) {
+ const vector<int8_t>& attestationApplicationId, //
+ const vector<int8_t>& attestationChallenge, //
+ vector<Certificate>* outCertificateChain) {
+ if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of attestationKey"));
+ "Error attestation certificate previously generated"));
}
- optional<vector<uint8_t>> attestationPrivKey =
- support::ecKeyPairGetPrivateKey(attestationKeyPair.value());
- if (!attestationPrivKey) {
+ 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);
+ if (!keyAttestationPair) {
+ LOG(ERROR) << "Error creating credentialKey and attestation";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of attestationKey"));
+ "Error creating credentialKey and attestation"));
}
- string serialDecimal;
- string issuer;
- string subject;
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+ vector<uint8_t> keyPair = keyAttestationPair.value().first;
+ certificateChain_ = keyAttestationPair.value().second;
- // First create a certificate for |credentialPubKey| which is signed by
- // |attestationPrivKey|.
- //
- serialDecimal = "0"; // TODO: set serial to |attestationChallenge|
- issuer = "Android Open Source Project";
- subject = "Android IdentityCredential CredentialKey";
- optional<vector<uint8_t>> credentialPubKeyCertificate = support::ecPublicKeyGenerateCertificate(
- credentialPubKey_, attestationPrivKey.value(), serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
- if (!credentialPubKeyCertificate) {
+ optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
+ if (!pubKey) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error creating certificate for credentialPubKey"));
+ "Error getting public part of credentialKey"));
}
+ credentialPubKey_ = pubKey.value();
- // This is followed by a certificate for |attestationPubKey| self-signed by
- // |attestationPrivKey|.
- serialDecimal = "0"; // TODO: set serial
- issuer = "Android Open Source Project";
- subject = "Android IdentityCredential AttestationKey";
- optional<vector<uint8_t>> attestationKeyCertificate = support::ecPublicKeyGenerateCertificate(
- attestationPubKey.value(), attestationPrivKey.value(), serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
- if (!attestationKeyCertificate) {
+ optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair);
+ if (!privKey) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error creating certificate for attestationPubKey"));
+ "Error getting private part of credentialKey"));
}
+ credentialPrivKey_ = privKey.value();
- // Concatenate the certificates to form the chain.
- vector<uint8_t> certificateChain;
- certificateChain.insert(certificateChain.end(), credentialPubKeyCertificate.value().begin(),
- credentialPubKeyCertificate.value().end());
- certificateChain.insert(certificateChain.end(), attestationKeyCertificate.value().begin(),
- attestationKeyCertificate.value().end());
-
- optional<vector<vector<uint8_t>>> splitCertChain =
- support::certificateChainSplit(certificateChain);
- if (!splitCertChain) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error splitting certificate chain"));
- }
+ // convert from vector<vector<uint8_t>>> to vector<Certificate>*
*outCertificateChain = vector<Certificate>();
- for (const vector<uint8_t>& cert : splitCertChain.value()) {
+ for (const vector<uint8_t>& cert : certificateChain_) {
Certificate c = Certificate();
c.encodedCertificate = byteStringToSigned(cert);
outCertificateChain->push_back(std::move(c));
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h
index b380f89..b182862 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/WritableIdentityCredential.h
@@ -64,10 +64,13 @@
string docType_;
bool testCredential_;
- // These are set in initialize().
+ // This is set in initialize().
vector<uint8_t> storageKey_;
+
+ // These are set in getAttestationCertificate().
vector<uint8_t> credentialPrivKey_;
vector<uint8_t> credentialPubKey_;
+ vector<vector<uint8_t>> certificateChain_;
// These fields are initialized during startPersonalization()
size_t numAccessControlProfileRemaining_;
diff --git a/identity/support/Android.bp b/identity/support/Android.bp
index 7b4546b..2b6c695 100644
--- a/identity/support/Android.bp
+++ b/identity/support/Android.bp
@@ -23,10 +23,14 @@
"include",
],
shared_libs: [
+ "android.hardware.keymaster@4.0",
"libcrypto",
"libbase",
"libhidlbase",
"libhardware",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
],
static_libs: [
"libcppbor",
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
index 4533ad9..507e914 100644
--- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -21,6 +21,7 @@
#include <optional>
#include <string>
#include <tuple>
+#include <utility>
#include <vector>
namespace android {
@@ -106,6 +107,17 @@
// ---------------------------------------------------------------------------
// EC crypto functionality / abstraction (only supports P-256).
// ---------------------------------------------------------------------------
+// Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the
+// PKCS#8 encoded key-pair. Also generates an attestation
+// certificate using the |challenge| and |applicationId|, and returns the generated
+// certificate in X.509 certificate chain format.
+//
+// The attestation time fields used will be the current time, and expires in one year.
+//
+// The first parameter of the return value is the keyPair generated, second return in
+// the pair is the attestation certificate generated.
+optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAndAttestation(
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId);
// Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the
// PKCS#8 encoded key-pair.
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index e2828bf..bf6a5c3 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -47,6 +47,13 @@
#include <cppbor.h>
#include <cppbor_parse.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <keymaster/authorization_set.h>
+#include <keymaster/contexts/pure_soft_keymaster_context.h>
+#include <keymaster/contexts/soft_attestation_cert.h>
+#include <keymaster/keymaster_tags.h>
+#include <keymaster/km_openssl/attestation_utils.h>
+
namespace android {
namespace hardware {
namespace identity {
@@ -816,6 +823,138 @@
return hmac;
}
+// Generates the attestation certificate with the parameters passed in. Note
+// that the passed in |activeTimeMilliSeconds| |expireTimeMilliSeconds| are in
+// milli seconds since epoch. We are setting them to milliseconds due to
+// requirement in AuthorizationSet KM_DATE fields. The certificate created is
+// actually in seconds.
+optional<vector<vector<uint8_t>>> createAttestation(const EVP_PKEY* key,
+ const vector<uint8_t>& applicationId,
+ const vector<uint8_t>& challenge,
+ uint64_t activeTimeMilliSeconds,
+ uint64_t expireTimeMilliSeconds) {
+ ::keymaster::AuthorizationSet auth_set(
+ ::keymaster::AuthorizationSetBuilder()
+ .Authorization(::keymaster::TAG_ATTESTATION_CHALLENGE, challenge.data(),
+ challenge.size())
+ .Authorization(::keymaster::TAG_ACTIVE_DATETIME, activeTimeMilliSeconds)
+ // Even though identity attestation hal said the application
+ // id should be in software enforced authentication set,
+ // keymaster portable lib expect the input in this
+ // parameter because the software enforced in input to keymaster
+ // refers to the key software enforced properties. And this
+ // parameter refers to properties of the attestation which
+ // includes app id.
+ .Authorization(::keymaster::TAG_ATTESTATION_APPLICATION_ID,
+ applicationId.data(), applicationId.size())
+ .Authorization(::keymaster::TAG_USAGE_EXPIRE_DATETIME, expireTimeMilliSeconds));
+
+ // Unique id and device id is not applicable for identity credential attestation,
+ // so we don't need to set those or application id.
+ ::keymaster::AuthorizationSet swEnforced(::keymaster::AuthorizationSetBuilder().Authorization(
+ ::keymaster::TAG_CREATION_DATETIME, activeTimeMilliSeconds));
+
+ ::keymaster::AuthorizationSet hwEnforced(
+ ::keymaster::AuthorizationSetBuilder()
+ .Authorization(::keymaster::TAG_PURPOSE, KM_PURPOSE_SIGN)
+ .Authorization(::keymaster::TAG_KEY_SIZE, 256)
+ .Authorization(::keymaster::TAG_ALGORITHM, KM_ALGORITHM_EC)
+ .Authorization(::keymaster::TAG_NO_AUTH_REQUIRED)
+ .Authorization(::keymaster::TAG_DIGEST, KM_DIGEST_SHA_2_256)
+ .Authorization(::keymaster::TAG_EC_CURVE, KM_EC_CURVE_P_256)
+ .Authorization(::keymaster::TAG_IDENTITY_CREDENTIAL_KEY));
+
+ const keymaster_cert_chain_t* attestation_chain =
+ ::keymaster::getAttestationChain(KM_ALGORITHM_EC, nullptr);
+
+ if (attestation_chain == nullptr) {
+ LOG(ERROR) << "Error getting attestation chain";
+ return {};
+ }
+
+ const keymaster_key_blob_t* attestation_signing_key =
+ ::keymaster::getAttestationKey(KM_ALGORITHM_EC, nullptr);
+ if (attestation_signing_key == nullptr) {
+ LOG(ERROR) << "Error getting attestation key";
+ return {};
+ }
+
+ keymaster_error_t error;
+ ::keymaster::CertChainPtr cert_chain_out;
+ ::keymaster::PureSoftKeymasterContext context;
+
+ // set identity version to 10 per hal requirements specified in IWriteableCredential.hal
+ // For now, the identity version in the attestation is set in the keymaster
+ // version field in the portable keymaster lib, which is a bit misleading.
+ uint identity_version = 10;
+ error = generate_attestation_from_EVP(key, swEnforced, hwEnforced, auth_set, context,
+ identity_version, *attestation_chain,
+ *attestation_signing_key, &cert_chain_out);
+
+ if (KM_ERROR_OK != error || !cert_chain_out) {
+ LOG(ERROR) << "Error generate attestation from EVP key" << error;
+ return {};
+ }
+
+ // translate certificate format from keymaster_cert_chain_t to vector<uint8_t>.
+ vector<vector<uint8_t>> attestationCertificate;
+ for (int i = 0; i < cert_chain_out->entry_count; i++) {
+ attestationCertificate.insert(
+ attestationCertificate.end(),
+ vector<uint8_t>(
+ cert_chain_out->entries[i].data,
+ cert_chain_out->entries[i].data + cert_chain_out->entries[i].data_length));
+ }
+
+ return attestationCertificate;
+}
+
+optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAndAttestation(
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+ auto ec_key = ::keymaster::EC_KEY_Ptr(EC_KEY_new());
+ auto pkey = ::keymaster::EVP_PKEY_Ptr(EVP_PKEY_new());
+ auto group = ::keymaster::EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+
+ if (ec_key.get() == nullptr || pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return {};
+ }
+
+ if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
+ EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
+ LOG(ERROR) << "Error generating key";
+ return {};
+ }
+
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()) != 1) {
+ LOG(ERROR) << "Error getting private key";
+ return {};
+ }
+
+ uint64_t now = time(nullptr);
+ uint64_t secondsInOneYear = 365 * 24 * 60 * 60;
+ uint64_t expireTimeMs = (now + secondsInOneYear) * 1000;
+
+ optional<vector<vector<uint8_t>>> attestationCert =
+ createAttestation(pkey.get(), applicationId, challenge, now * 1000, expireTimeMs);
+ if (!attestationCert) {
+ LOG(ERROR) << "Error create attestation from key and challenge";
+ return {};
+ }
+
+ int size = i2d_PrivateKey(pkey.get(), nullptr);
+ if (size == 0) {
+ LOG(ERROR) << "Error generating public key encoding";
+ return {};
+ }
+
+ vector<uint8_t> keyPair(size);
+ unsigned char* p = keyPair.data();
+ i2d_PrivateKey(pkey.get(), &p);
+
+ return make_pair(keyPair, attestationCert.value());
+}
+
optional<vector<uint8_t>> createEcKeyPair() {
auto ec_key = EC_KEY_Ptr(EC_KEY_new());
auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());