identity: Fix attestation and documentation problems.
- The docs said that IdentityCredential.createEphemeralKey() returned
data encoded PKCS#8 which is wrong. It's supposed to be in DER format
which is also what the VTS tests and credstore expects.
- Clarify that createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
and createAuthChallenge() are all optional.
- Avoid passing an invalid profile ID in the IdentityCredentialTests.
verifyOneProfileAndEntryPass test.
- Update requirements for which tags must be present in the attestation
for CredentialKey as well as the requirements on expiration date and
the issuer name. Update default implementation to satisfy these
requirements. Update VTS tests to carefully verify these requrements
are met.
- Clarify requirements for X.509 cert for AuthenticationKey. Add VTS
test to verify.
- Mandate that TAG_IDENTITY_CREDENTIAL_KEY must not be set for test
credentials. Add VTS test to verify this.
- Make default implementation pretend to be implemented in a trusted
environment and streamline VTS tests to not special-case for the
default implementation.
- Switch to using the attestation extension parser from the KM 4.1
support library instead of the one from system/keymaster. The latter
one did not support the latest attestation extension and thus would
fail for pretty much anything that wasn't the default HAL impl.
- Fix a couple of bugs in keymaster::V4_1::parse_attestation_record():
- Report root_of_trust.security_level
- Add support for Tag::IDENTITY_CREDENTIAL_KEY
- Fix how EMacKey is calculated.
- Add test vectors to verify how EMacKey and DeviceMac is calculated.
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Bug: 171745570
Change-Id: I2f8bd772de078556733f769cec2021918d1d7de6
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index 747f182..fbf3ab1 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -44,6 +44,7 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <charconv>
#include <cppbor.h>
#include <cppbor_parse.h>
@@ -870,16 +871,97 @@
return hmac;
}
+int parseDigits(const char** s, int numDigits) {
+ int result;
+ auto [_, ec] = std::from_chars(*s, *s + numDigits, result);
+ if (ec != std::errc()) {
+ LOG(ERROR) << "Error parsing " << numDigits << " digits "
+ << " from " << s;
+ return 0;
+ }
+ *s += numDigits;
+ return result;
+}
+
+bool parseAsn1Time(const ASN1_TIME* asn1Time, time_t* outTime) {
+ struct tm tm;
+
+ memset(&tm, '\0', sizeof(tm));
+ const char* timeStr = (const char*)asn1Time->data;
+ const char* s = timeStr;
+ if (asn1Time->type == V_ASN1_UTCTIME) {
+ tm.tm_year = parseDigits(&s, 2);
+ if (tm.tm_year < 70) {
+ tm.tm_year += 100;
+ }
+ } else if (asn1Time->type == V_ASN1_GENERALIZEDTIME) {
+ tm.tm_year = parseDigits(&s, 4) - 1900;
+ tm.tm_year -= 1900;
+ } else {
+ LOG(ERROR) << "Unsupported ASN1_TIME type " << asn1Time->type;
+ return false;
+ }
+ tm.tm_mon = parseDigits(&s, 2) - 1;
+ tm.tm_mday = parseDigits(&s, 2);
+ tm.tm_hour = parseDigits(&s, 2);
+ tm.tm_min = parseDigits(&s, 2);
+ tm.tm_sec = parseDigits(&s, 2);
+ // This may need to be updated if someone create certificates using +/- instead of Z.
+ //
+ if (*s != 'Z') {
+ LOG(ERROR) << "Expected Z in string '" << timeStr << "' at offset " << (s - timeStr);
+ return false;
+ }
+
+ time_t t = timegm(&tm);
+ if (t == -1) {
+ LOG(ERROR) << "Error converting broken-down time to time_t";
+ return false;
+ }
+ *outTime = t;
+ return true;
+}
+
// 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) {
+//
+// If 0 is passed for expiration time, the expiration time from batch
+// certificate will be used.
+//
+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, bool isTestCredential) {
+ const keymaster_cert_chain_t* attestation_chain =
+ ::keymaster::getAttestationChain(KM_ALGORITHM_EC, nullptr);
+ if (attestation_chain == nullptr) {
+ LOG(ERROR) << "Error getting attestation chain";
+ return {};
+ }
+ if (expireTimeMilliSeconds == 0) {
+ if (attestation_chain->entry_count < 1) {
+ LOG(ERROR) << "Expected at least one entry in attestation chain";
+ return {};
+ }
+ keymaster_blob_t* bcBlob = &(attestation_chain->entries[0]);
+ const uint8_t* bcData = bcBlob->data;
+ auto bc = X509_Ptr(d2i_X509(nullptr, &bcData, bcBlob->data_length));
+ time_t bcNotAfter;
+ if (!parseAsn1Time(X509_get0_notAfter(bc.get()), &bcNotAfter)) {
+ LOG(ERROR) << "Error getting notAfter from batch certificate";
+ return {};
+ }
+ expireTimeMilliSeconds = bcNotAfter * 1000;
+ }
+ 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::AuthorizationSet auth_set(
::keymaster::AuthorizationSetBuilder()
.Authorization(::keymaster::TAG_ATTESTATION_CHALLENGE, challenge.data(),
@@ -901,7 +983,7 @@
::keymaster::AuthorizationSet swEnforced(::keymaster::AuthorizationSetBuilder().Authorization(
::keymaster::TAG_CREATION_DATETIME, activeTimeMilliSeconds));
- ::keymaster::AuthorizationSet hwEnforced(
+ ::keymaster::AuthorizationSetBuilder hwEnforcedBuilder =
::keymaster::AuthorizationSetBuilder()
.Authorization(::keymaster::TAG_PURPOSE, KM_PURPOSE_SIGN)
.Authorization(::keymaster::TAG_KEY_SIZE, 256)
@@ -909,34 +991,29 @@
.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));
+ .Authorization(::keymaster::TAG_OS_VERSION, 42)
+ .Authorization(::keymaster::TAG_OS_PATCHLEVEL, 43);
- const keymaster_cert_chain_t* attestation_chain =
- ::keymaster::getAttestationChain(KM_ALGORITHM_EC, nullptr);
-
- if (attestation_chain == nullptr) {
- LOG(ERROR) << "Error getting attestation chain";
- return {};
+ // Only include TAG_IDENTITY_CREDENTIAL_KEY if it's not a test credential
+ if (!isTestCredential) {
+ hwEnforcedBuilder.Authorization(::keymaster::TAG_IDENTITY_CREDENTIAL_KEY);
}
-
- 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::AuthorizationSet hwEnforced(hwEnforcedBuilder);
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);
+ // Pretend to be implemented in a trusted environment just so we can pass
+ // the VTS tests. Of course, this is a pretend-only game since hopefully no
+ // relying party is ever going to trust our batch key and those keys above
+ // it.
+ //
+ ::keymaster::PureSoftKeymasterContext context(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
+
+ error = generate_attestation_from_EVP_with_subject_name(
+ key, swEnforced, hwEnforced, auth_set, context, ::keymaster::kCurrentKeymasterVersion,
+ *attestation_chain, *attestation_signing_key, "Android Identity Credential Key",
+ &cert_chain_out);
if (KM_ERROR_OK != error || !cert_chain_out) {
LOG(ERROR) << "Error generate attestation from EVP key" << error;
@@ -957,7 +1034,8 @@
}
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAndAttestation(
- const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId,
+ bool isTestCredential) {
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));
@@ -978,12 +1056,11 @@
return {};
}
- uint64_t now = time(nullptr);
- uint64_t secondsInOneYear = 365 * 24 * 60 * 60;
- uint64_t expireTimeMs = (now + secondsInOneYear) * 1000;
+ uint64_t nowMs = time(nullptr) * 1000;
+ uint64_t expireTimeMs = 0; // Set to same as batch certificate
- optional<vector<vector<uint8_t>>> attestationCert =
- createAttestation(pkey.get(), applicationId, challenge, now * 1000, expireTimeMs);
+ optional<vector<vector<uint8_t>>> attestationCert = createAttestation(
+ pkey.get(), applicationId, challenge, nowMs, expireTimeMs, isTestCredential);
if (!attestationCert) {
LOG(ERROR) << "Error create attestation from key and challenge";
return {};
@@ -1031,14 +1108,12 @@
return {};
}
- uint64_t now = (std::chrono::duration_cast<std::chrono::nanoseconds>(
- std::chrono::system_clock::now().time_since_epoch()).
- count()/ 1000000000);
- uint64_t secondsInOneYear = 365 * 24 * 60 * 60;
- uint64_t expireTimeMs = (now + secondsInOneYear) * 1000;
+ uint64_t nowMs = time(nullptr) * 1000;
+ uint64_t expireTimeMs = 0; // Set to same as batch certificate
optional<vector<vector<uint8_t>>> attestationCert =
- createAttestation(pkey.get(), applicationId, challenge, now * 1000, expireTimeMs);
+ createAttestation(pkey.get(), applicationId, challenge, nowMs, expireTimeMs,
+ false /* isTestCredential */);
if (!attestationCert) {
LOG(ERROR) << "Error create attestation from key and challenge";
return {};
@@ -1646,6 +1721,32 @@
return std::make_pair(tbsCertificateOffset, tbsCertificateSize);
}
+optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x509Certificate) {
+ vector<X509_Ptr> certs;
+ if (!parseX509Certificates(x509Certificate, certs)) {
+ LOG(ERROR) << "Error parsing certificates";
+ return {};
+ }
+ if (certs.size() < 1) {
+ LOG(ERROR) << "No certificates in chain";
+ return {};
+ }
+
+ time_t notBefore;
+ time_t notAfter;
+ if (!parseAsn1Time(X509_get0_notBefore(certs[0].get()), ¬Before)) {
+ LOG(ERROR) << "Error parsing notBefore";
+ return {};
+ }
+
+ if (!parseAsn1Time(X509_get0_notAfter(certs[0].get()), ¬After)) {
+ LOG(ERROR) << "Error parsing notAfter";
+ return {};
+ }
+
+ return std::make_pair(notBefore, notAfter);
+}
+
optional<pair<size_t, size_t>> certificateFindSignature(const vector<uint8_t>& x509Certificate) {
vector<X509_Ptr> certs;
if (!parseX509Certificates(x509Certificate, certs)) {
@@ -2218,6 +2319,49 @@
// Utility functions specific to IdentityCredential.
// ---------------------------------------------------------------------------
+optional<vector<uint8_t>> calcEMacKey(const vector<uint8_t>& privateKey,
+ const vector<uint8_t>& publicKey,
+ const vector<uint8_t>& sessionTranscriptBytes) {
+ optional<vector<uint8_t>> sharedSecret = support::ecdh(publicKey, privateKey);
+ if (!sharedSecret) {
+ LOG(ERROR) << "Error performing ECDH";
+ return {};
+ }
+ vector<uint8_t> salt = support::sha256(sessionTranscriptBytes);
+ vector<uint8_t> info = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+ if (!derivedKey) {
+ LOG(ERROR) << "Error performing HKDF";
+ return {};
+ }
+ return derivedKey.value();
+}
+
+optional<vector<uint8_t>> calcMac(const vector<uint8_t>& sessionTranscriptEncoded,
+ const string& docType,
+ const vector<uint8_t>& deviceNameSpacesEncoded,
+ const vector<uint8_t>& eMacKey) {
+ auto [sessionTranscriptItem, _, errMsg] = cppbor::parse(sessionTranscriptEncoded);
+ if (sessionTranscriptItem == nullptr) {
+ LOG(ERROR) << "Error parsing sessionTranscriptEncoded: " << errMsg;
+ return {};
+ }
+ // The data that is MACed is ["DeviceAuthentication", sessionTranscript, docType,
+ // deviceNameSpacesBytes] so build up that structure
+ cppbor::Array deviceAuthentication =
+ cppbor::Array()
+ .add("DeviceAuthentication")
+ .add(std::move(sessionTranscriptItem))
+ .add(docType)
+ .add(cppbor::Semantic(kSemanticTagEncodedCbor, deviceNameSpacesEncoded));
+ vector<uint8_t> deviceAuthenticationBytes =
+ cppbor::Semantic(kSemanticTagEncodedCbor, deviceAuthentication.encode()).encode();
+ optional<vector<uint8_t>> calculatedMac =
+ support::coseMac0(eMacKey, {}, // payload
+ deviceAuthenticationBytes); // detached content
+ return calculatedMac;
+}
+
vector<vector<uint8_t>> chunkVector(const vector<uint8_t>& content, size_t maxChunkSize) {
vector<vector<uint8_t>> ret;