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: Ic906fa24baa3a475585e543dc03aaf1d0f8d19a0 Merged-In: I2f8bd772de078556733f769cec2021918d1d7de6
This commit is contained in:
parent
685b5e6049
commit
f5372106cd
17 changed files with 930 additions and 616 deletions
|
@ -55,7 +55,7 @@ interface IIdentityCredential {
|
|||
* This method may only be called once per instance. If called more than once, STATUS_FAILED
|
||||
* will be returned.
|
||||
*
|
||||
* @return the unencrypted key-pair in PKCS#8 format.
|
||||
* @return the private key, in DER format as specified in RFC 5915.
|
||||
*/
|
||||
byte[] createEphemeralKeyPair();
|
||||
|
||||
|
@ -88,10 +88,10 @@ interface IIdentityCredential {
|
|||
* The setRequestedNamespaces() and setVerificationToken() methods will be called before
|
||||
* this method is called.
|
||||
*
|
||||
* This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
|
||||
* createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
|
||||
* multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
|
||||
* finishRetrieval().
|
||||
* This method is called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
|
||||
* createAuthChallenge() (note that those calls are optional) and before startRetrieveEntry().
|
||||
* This method call is followed by multiple calls of startRetrieveEntryValue(),
|
||||
* retrieveEntryValue(), and finally finishRetrieval().
|
||||
*
|
||||
* It is permissible to perform data retrievals multiple times using the same instance (e.g.
|
||||
* startRetrieval(), then multiple calls of startRetrieveEntryValue(), retrieveEntryValue(),
|
||||
|
@ -343,12 +343,13 @@ interface IIdentityCredential {
|
|||
*
|
||||
* - signature: must be set to ECDSA.
|
||||
*
|
||||
* - subject: CN shall be set to "Android Identity Credential Authentication Key".
|
||||
* - subject: CN shall be set to "Android Identity Credential Authentication Key". (fixed
|
||||
* value: same on all certs)
|
||||
*
|
||||
* - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the
|
||||
* values returned in HardwareInformation.
|
||||
* - issuer: CN shall be set to "Android Identity Credential Key". (fixed value:
|
||||
* same on all certs)
|
||||
*
|
||||
* - validity: should be from current time and one year in the future.
|
||||
* - validity: should be from current time and one year in the future (365 days).
|
||||
*
|
||||
* - subjectPublicKeyInfo: must contain attested public key.
|
||||
*
|
||||
|
|
|
@ -37,12 +37,12 @@ interface IWritableIdentityCredential {
|
|||
*
|
||||
* - signature: must be set to ECDSA.
|
||||
*
|
||||
* - subject: CN shall be set to "Android Identity Credential Key".
|
||||
* - subject: CN shall be set to "Android Identity Credential Key". (fixed value:
|
||||
* same on all certs)
|
||||
*
|
||||
* - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the
|
||||
* values returned in HardwareInformation.
|
||||
* - issuer: Same as the subject field of the batch attestation key.
|
||||
*
|
||||
* - validity: should be from current time and expire at the same time as the
|
||||
* - validity: Should be set to current time and expire at the same time as the
|
||||
* attestation batch certificate used.
|
||||
*
|
||||
* - subjectPublicKeyInfo: must contain attested public key.
|
||||
|
@ -55,19 +55,14 @@ interface IWritableIdentityCredential {
|
|||
*
|
||||
* - The attestationSecurityLevel field must be set to either Software (0),
|
||||
* TrustedEnvironment (1), or StrongBox (2) depending on how attestation is
|
||||
* implemented. Only the default AOSP implementation of this HAL may use
|
||||
* value 0 (additionally, this implementation must not be used on production
|
||||
* devices).
|
||||
* implemented.
|
||||
*
|
||||
* - The keymasterVersion field in the attestation extension must be set to (10*major + minor)
|
||||
* where major and minor are the Identity Credential interface major and minor versions.
|
||||
* Specifically for this version of the interface (1.0) this value is 10.
|
||||
* - The keymasterVersion field in the attestation extension must be set to the.
|
||||
* same value as used for Android Keystore keys.
|
||||
*
|
||||
* - The keymasterSecurityLevel field in the attestation extension must be set to
|
||||
* either Software (0), TrustedEnvironment (1), or StrongBox (2) depending on how
|
||||
* the Trusted Application backing the HAL implementation is implemented. Only
|
||||
* the default AOSP implementation of this HAL may use value 0 (additionally, this
|
||||
* implementation must not be used on production devices)
|
||||
* the Trusted Application backing the HAL implementation is implemented.
|
||||
*
|
||||
* - The attestationChallenge field must be set to the passed-in challenge.
|
||||
*
|
||||
|
@ -81,7 +76,8 @@ interface IWritableIdentityCredential {
|
|||
*
|
||||
* - Tag::IDENTITY_CREDENTIAL_KEY which indicates that the key is an Identity
|
||||
* Credential key (which can only sign/MAC very specific messages) and not an Android
|
||||
* Keystore key (which can be used to sign/MAC anything).
|
||||
* Keystore key (which can be used to sign/MAC anything). This must not be set
|
||||
* for test credentials.
|
||||
*
|
||||
* - Tag::PURPOSE must be set to SIGN
|
||||
*
|
||||
|
@ -95,10 +91,13 @@ interface IWritableIdentityCredential {
|
|||
*
|
||||
* - Tag::EC_CURVE must be set to P_256
|
||||
*
|
||||
* Additional authorizations may be needed in the softwareEnforced and teeEnforced
|
||||
* fields - the above is not an exhaustive list. Specifically, authorizations containing
|
||||
* information about the root of trust, OS version, verified boot state, and so on should
|
||||
* be included.
|
||||
* - Tag::ROOT_OF_TRUST must be set
|
||||
*
|
||||
* - Tag::OS_VERSION and Tag::OS_PATCHLEVEL must be set
|
||||
*
|
||||
* Additional authorizations may be appear in the softwareEnforced and teeEnforced
|
||||
* fields. For example if the device has a boot or vendor partitions, then BOOT_PATCHLEVEL
|
||||
* and VENDOR_PATCHLEVEL should be set.
|
||||
*
|
||||
* Since the chain is required to be generated using Keymaster Attestation, the returned
|
||||
* certificate chain has the following properties:
|
||||
|
@ -112,8 +111,8 @@ interface IWritableIdentityCredential {
|
|||
* As with any user of attestation, the Issuing Authority (as a relying party) wishing
|
||||
* to issue a credential to a device using these APIs, must carefully examine the
|
||||
* returned certificate chain for all of the above (and more). In particular, the Issuing
|
||||
* Authority should check the root of trust, verified boot state, patch level,
|
||||
* application id, etc.
|
||||
* Authority should check the root of trust (which include verified boot state), patch level,
|
||||
* attestation application id, etc.
|
||||
*
|
||||
* This all depends on the needs of the Issuing Authority and the kind of credential but
|
||||
* in general an Issuing Authority should never issue a credential to a device without
|
||||
|
|
|
@ -276,6 +276,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
|||
auto itemsRequest = byteStringToUnsigned(itemsRequestS);
|
||||
auto readerSignature = byteStringToUnsigned(readerSignatureS);
|
||||
|
||||
std::unique_ptr<cppbor::Item> sessionTranscriptItem;
|
||||
if (sessionTranscript.size() > 0) {
|
||||
auto [item, _, message] = cppbor::parse(sessionTranscript);
|
||||
if (item == nullptr) {
|
||||
|
@ -283,7 +284,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
|||
IIdentityCredentialStore::STATUS_INVALID_DATA,
|
||||
"SessionTranscript contains invalid CBOR"));
|
||||
}
|
||||
sessionTranscriptItem_ = std::move(item);
|
||||
sessionTranscriptItem = std::move(item);
|
||||
}
|
||||
if (numStartRetrievalCalls_ > 0) {
|
||||
if (sessionTranscript_ != sessionTranscript) {
|
||||
|
@ -323,7 +324,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
|||
vector<uint8_t> encodedReaderAuthentication =
|
||||
cppbor::Array()
|
||||
.add("ReaderAuthentication")
|
||||
.add(sessionTranscriptItem_->clone())
|
||||
.add(std::move(sessionTranscriptItem))
|
||||
.add(cppbor::Semantic(24, itemsRequestBytes))
|
||||
.encode();
|
||||
vector<uint8_t> encodedReaderAuthenticationBytes =
|
||||
|
@ -781,13 +782,6 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
|
|||
optional<vector<uint8_t>> mac;
|
||||
if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
|
||||
readerPublicKey_.size() > 0) {
|
||||
cppbor::Array array;
|
||||
array.add("DeviceAuthentication");
|
||||
array.add(sessionTranscriptItem_->clone());
|
||||
array.add(docType_);
|
||||
array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
|
||||
vector<uint8_t> deviceAuthenticationBytes = cppbor::Semantic(24, array.encode()).encode();
|
||||
|
||||
vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
|
||||
optional<vector<uint8_t>> signingKey =
|
||||
support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
|
||||
|
@ -797,31 +791,15 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
|
|||
"Error decrypting signingKeyBlob"));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> sharedSecret =
|
||||
support::ecdh(readerPublicKey_, signingKey.value());
|
||||
if (!sharedSecret) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
|
||||
}
|
||||
|
||||
// Mix-in SessionTranscriptBytes
|
||||
vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
|
||||
vector<uint8_t> sharedSecretWithSessionTranscriptBytes = sharedSecret.value();
|
||||
std::copy(sessionTranscriptBytes.begin(), sessionTranscriptBytes.end(),
|
||||
std::back_inserter(sharedSecretWithSessionTranscriptBytes));
|
||||
|
||||
vector<uint8_t> salt = {0x00};
|
||||
vector<uint8_t> info = {};
|
||||
optional<vector<uint8_t>> derivedKey =
|
||||
support::hkdf(sharedSecretWithSessionTranscriptBytes, salt, info, 32);
|
||||
if (!derivedKey) {
|
||||
optional<vector<uint8_t>> eMacKey =
|
||||
support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
|
||||
if (!eMacKey) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error deriving key from shared secret"));
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
|
||||
}
|
||||
|
||||
mac = support::coseMac0(derivedKey.value(), {}, // payload
|
||||
deviceAuthenticationBytes); // detached content
|
||||
mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
|
||||
eMacKey.value());
|
||||
if (!mac) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
|
||||
|
@ -835,9 +813,9 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
|
|||
|
||||
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
|
||||
vector<int8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
|
||||
string serialDecimal = "0"; // TODO: set serial to something unique
|
||||
string issuer = "Android Open Source Project";
|
||||
string subject = "Android IdentityCredential Reference Implementation";
|
||||
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;
|
||||
|
||||
|
|
|
@ -103,7 +103,6 @@ class IdentityCredential : public BnIdentityCredential {
|
|||
map<int32_t, int> profileIdToAccessCheckResult_;
|
||||
vector<uint8_t> signingKeyBlob_;
|
||||
vector<uint8_t> sessionTranscript_;
|
||||
std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
|
||||
vector<uint8_t> itemsRequest_;
|
||||
vector<int32_t> requestCountsRemaining_;
|
||||
map<string, set<string>> requestedNameSpacesAndNames_;
|
||||
|
|
|
@ -74,7 +74,7 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
|
|||
vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
|
||||
|
||||
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
|
||||
support::createEcKeyPairAndAttestation(challenge, appId);
|
||||
support::createEcKeyPairAndAttestation(challenge, appId, testCredential_);
|
||||
if (!keyAttestationPair) {
|
||||
LOG(ERROR) << "Error creating credentialKey and attestation";
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
|
|
|
@ -9,7 +9,6 @@ cc_test {
|
|||
"VtsIWritableIdentityCredentialTests.cpp",
|
||||
"VtsIdentityTestUtils.cpp",
|
||||
"VtsAttestationTests.cpp",
|
||||
"VtsAttestationParserSupport.cpp",
|
||||
"UserAuthTests.cpp",
|
||||
"ReaderAuthTests.cpp",
|
||||
],
|
||||
|
@ -20,13 +19,14 @@ cc_test {
|
|||
static_libs: [
|
||||
"libcppbor",
|
||||
"libkeymaster_portable",
|
||||
"libsoft_attestation_cert",
|
||||
"libpuresoftkeymasterdevice",
|
||||
"android.hardware.keymaster@4.0",
|
||||
"android.hardware.identity-support-lib",
|
||||
"android.hardware.identity-cpp",
|
||||
"android.hardware.keymaster-cpp",
|
||||
"android.hardware.keymaster-ndk_platform",
|
||||
"libkeymaster4support",
|
||||
"libkeymaster4_1support",
|
||||
],
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
|
|
|
@ -1,187 +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.
|
||||
*/
|
||||
|
||||
#include "VtsAttestationParserSupport.h"
|
||||
|
||||
#include <aidl/Gtest.h>
|
||||
#include <map>
|
||||
|
||||
namespace android::hardware::identity::test_utils {
|
||||
|
||||
using std::endl;
|
||||
using std::map;
|
||||
using std::optional;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using ::android::sp;
|
||||
using ::android::String16;
|
||||
using ::android::binder::Status;
|
||||
|
||||
using ::keymaster::ASN1_OBJECT_Ptr;
|
||||
using ::keymaster::AuthorizationSet;
|
||||
using ::keymaster::EVP_PKEY_Ptr;
|
||||
using ::keymaster::kAttestionRecordOid;
|
||||
using ::keymaster::TAG_ATTESTATION_APPLICATION_ID;
|
||||
using ::keymaster::TAG_IDENTITY_CREDENTIAL_KEY;
|
||||
using ::keymaster::TAG_INCLUDE_UNIQUE_ID;
|
||||
using ::keymaster::TypedTag;
|
||||
using ::keymaster::X509_Ptr;
|
||||
|
||||
using support::certificateChainSplit;
|
||||
|
||||
optional<keymaster_cert_chain_t> AttestationCertificateParser::certificateChainToKeymasterChain(
|
||||
const vector<Certificate>& certificates) {
|
||||
if (certificates.size() <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
keymaster_cert_chain_t kCert;
|
||||
kCert.entry_count = certificates.size();
|
||||
kCert.entries = (keymaster_blob_t*)malloc(sizeof(keymaster_blob_t) * kCert.entry_count);
|
||||
|
||||
int index = 0;
|
||||
for (const auto& c : certificates) {
|
||||
kCert.entries[index].data_length = c.encodedCertificate.size();
|
||||
uint8_t* data = (uint8_t*)malloc(c.encodedCertificate.size());
|
||||
|
||||
memcpy(data, c.encodedCertificate.data(), c.encodedCertificate.size());
|
||||
kCert.entries[index].data = (const uint8_t*)data;
|
||||
index++;
|
||||
}
|
||||
|
||||
return kCert;
|
||||
}
|
||||
|
||||
bool AttestationCertificateParser::parse() {
|
||||
optional<keymaster_cert_chain_t> cert_chain = certificateChainToKeymasterChain(origCertChain_);
|
||||
if (!cert_chain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cert_chain.value().entry_count < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verifyChain(cert_chain.value())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verifyAttestationRecord(cert_chain.value().entries[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keymaster_free_cert_chain(&cert_chain.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
ASN1_OCTET_STRING* AttestationCertificateParser::getAttestationRecord(X509* certificate) {
|
||||
ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1));
|
||||
if (!oid.get()) return nullptr;
|
||||
|
||||
int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1);
|
||||
if (location == -1) return nullptr;
|
||||
|
||||
X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location);
|
||||
if (!attest_rec_ext) return nullptr;
|
||||
|
||||
ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext);
|
||||
return attest_rec;
|
||||
}
|
||||
|
||||
X509* AttestationCertificateParser::parseCertBlob(const keymaster_blob_t& blob) {
|
||||
const uint8_t* p = blob.data;
|
||||
return d2i_X509(nullptr, &p, blob.data_length);
|
||||
}
|
||||
|
||||
bool AttestationCertificateParser::verifyAttestationRecord(
|
||||
const keymaster_blob_t& attestation_cert) {
|
||||
X509_Ptr cert(parseCertBlob(attestation_cert));
|
||||
if (!cert.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ASN1_OCTET_STRING* attest_rec = getAttestationRecord(cert.get());
|
||||
if (!attest_rec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keymaster_blob_t att_unique_id = {};
|
||||
keymaster_blob_t att_challenge;
|
||||
keymaster_error_t ret = parse_attestation_record(
|
||||
attest_rec->data, attest_rec->length, &att_attestation_version_,
|
||||
&att_attestation_security_level_, &att_keymaster_version_,
|
||||
&att_keymaster_security_level_, &att_challenge, &att_sw_enforced_, &att_hw_enforced_,
|
||||
&att_unique_id);
|
||||
if (ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
att_challenge_.assign(att_challenge.data, att_challenge.data + att_challenge.data_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AttestationCertificateParser::getKeymasterVersion() {
|
||||
return att_keymaster_version_;
|
||||
}
|
||||
|
||||
uint32_t AttestationCertificateParser::getAttestationVersion() {
|
||||
return att_attestation_version_;
|
||||
}
|
||||
|
||||
vector<uint8_t> AttestationCertificateParser::getAttestationChallenge() {
|
||||
return att_challenge_;
|
||||
}
|
||||
|
||||
keymaster_security_level_t AttestationCertificateParser::getKeymasterSecurityLevel() {
|
||||
return att_keymaster_security_level_;
|
||||
}
|
||||
|
||||
keymaster_security_level_t AttestationCertificateParser::getAttestationSecurityLevel() {
|
||||
return att_attestation_security_level_;
|
||||
}
|
||||
|
||||
// Verify the Attestation certificates are correctly chained.
|
||||
bool AttestationCertificateParser::verifyChain(const keymaster_cert_chain_t& chain) {
|
||||
for (size_t i = 0; i < chain.entry_count - 1; ++i) {
|
||||
keymaster_blob_t& key_cert_blob = chain.entries[i];
|
||||
keymaster_blob_t& signing_cert_blob = chain.entries[i + 1];
|
||||
|
||||
X509_Ptr key_cert(parseCertBlob(key_cert_blob));
|
||||
X509_Ptr signing_cert(parseCertBlob(signing_cert_blob));
|
||||
if (!key_cert.get() || !signing_cert.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get()));
|
||||
if (!signing_pubkey.get()) return false;
|
||||
|
||||
if (X509_verify(key_cert.get(), signing_pubkey.get()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i + 1 == chain.entry_count - 1) {
|
||||
// Last entry is self-signed.
|
||||
if (X509_verify(signing_cert.get(), signing_pubkey.get()) != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::identity::test_utils
|
|
@ -1,122 +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 VTS_ATTESTATION_PARSER_SUPPORT_H
|
||||
#define VTS_ATTESTATION_PARSER_SUPPORT_H
|
||||
|
||||
//#include <aidl/Gtest.h>
|
||||
#include <android/hardware/identity/IIdentityCredentialStore.h>
|
||||
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
||||
#include <android/hardware/keymaster/4.0/types.h>
|
||||
#include <hardware/keymaster_defs.h>
|
||||
#include <keymaster/android_keymaster_utils.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>
|
||||
#include <vector>
|
||||
|
||||
namespace android::hardware::identity::test_utils {
|
||||
|
||||
using ::std::optional;
|
||||
using ::std::string;
|
||||
using ::std::vector;
|
||||
|
||||
using ::keymaster::AuthorizationSet;
|
||||
using ::keymaster::TypedTag;
|
||||
|
||||
class AttestationCertificateParser {
|
||||
public:
|
||||
AttestationCertificateParser(const vector<Certificate>& certChain)
|
||||
: origCertChain_(certChain) {}
|
||||
|
||||
bool parse();
|
||||
|
||||
uint32_t getKeymasterVersion();
|
||||
uint32_t getAttestationVersion();
|
||||
vector<uint8_t> getAttestationChallenge();
|
||||
keymaster_security_level_t getKeymasterSecurityLevel();
|
||||
keymaster_security_level_t getAttestationSecurityLevel();
|
||||
|
||||
template <keymaster_tag_t Tag>
|
||||
bool getSwEnforcedBool(TypedTag<KM_BOOL, Tag> tag) {
|
||||
if (att_sw_enforced_.GetTagValue(tag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <keymaster_tag_t Tag>
|
||||
bool getHwEnforcedBool(TypedTag<KM_BOOL, Tag> tag) {
|
||||
if (att_hw_enforced_.GetTagValue(tag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <keymaster_tag_t Tag>
|
||||
optional<vector<uint8_t>> getHwEnforcedBlob(TypedTag<KM_BYTES, Tag> tag) {
|
||||
keymaster_blob_t blob;
|
||||
if (att_hw_enforced_.GetTagValue(tag, &blob)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<uint8_t> ret(blob.data, blob.data + blob.data_length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <keymaster_tag_t Tag>
|
||||
optional<vector<uint8_t>> getSwEnforcedBlob(TypedTag<KM_BYTES, Tag> tag) {
|
||||
keymaster_blob_t blob;
|
||||
if (!att_sw_enforced_.GetTagValue(tag, &blob)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<uint8_t> ret(blob.data, blob.data + blob.data_length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
// Helper functions.
|
||||
bool verifyChain(const keymaster_cert_chain_t& chain);
|
||||
|
||||
ASN1_OCTET_STRING* getAttestationRecord(X509* certificate);
|
||||
|
||||
X509* parseCertBlob(const keymaster_blob_t& blob);
|
||||
|
||||
bool verifyAttestationRecord(const keymaster_blob_t& attestation_cert);
|
||||
|
||||
optional<keymaster_cert_chain_t> certificateChainToKeymasterChain(
|
||||
const vector<Certificate>& certificates);
|
||||
|
||||
// Private variables.
|
||||
vector<Certificate> origCertChain_;
|
||||
AuthorizationSet att_sw_enforced_;
|
||||
AuthorizationSet att_hw_enforced_;
|
||||
uint32_t att_attestation_version_;
|
||||
uint32_t att_keymaster_version_;
|
||||
keymaster_security_level_t att_attestation_security_level_;
|
||||
keymaster_security_level_t att_keymaster_security_level_;
|
||||
vector<uint8_t> att_challenge_;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::identity::test_utils
|
||||
|
||||
#endif // VTS_ATTESTATION_PARSER_SUPPORT_H
|
|
@ -29,7 +29,6 @@
|
|||
#include <future>
|
||||
#include <map>
|
||||
|
||||
#include "VtsAttestationParserSupport.h"
|
||||
#include "VtsIdentityTestUtils.h"
|
||||
|
||||
namespace android::hardware::identity {
|
||||
|
@ -44,7 +43,6 @@ using ::android::sp;
|
|||
using ::android::String16;
|
||||
using ::android::binder::Status;
|
||||
|
||||
using test_utils::AttestationCertificateParser;
|
||||
using test_utils::setupWritableCredential;
|
||||
using test_utils::validateAttestationCertificate;
|
||||
|
||||
|
@ -61,38 +59,12 @@ class VtsAttestationTests : public testing::TestWithParam<std::string> {
|
|||
sp<IIdentityCredentialStore> credentialStore_;
|
||||
};
|
||||
|
||||
TEST_P(VtsAttestationTests, verifyAttestationWithNonemptyChallengeEmptyId) {
|
||||
Status result;
|
||||
|
||||
HardwareInformation hwInfo;
|
||||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
|
||||
|
||||
string challenge = "NotSoRandomChallenge";
|
||||
vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
|
||||
vector<Certificate> attestationCertificate;
|
||||
vector<uint8_t> attestationApplicationId = {};
|
||||
|
||||
result = writableCredential->getAttestationCertificate(
|
||||
attestationApplicationId, attestationChallenge, &attestationCertificate);
|
||||
|
||||
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
|
||||
<< endl;
|
||||
|
||||
EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, hwInfo));
|
||||
}
|
||||
|
||||
TEST_P(VtsAttestationTests, verifyAttestationWithNonemptyChallengeNonemptyId) {
|
||||
Status result;
|
||||
|
||||
HardwareInformation hwInfo;
|
||||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1";
|
||||
vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
|
||||
|
@ -106,18 +78,16 @@ TEST_P(VtsAttestationTests, verifyAttestationWithNonemptyChallengeNonemptyId) {
|
|||
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
|
||||
<< endl;
|
||||
|
||||
EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, hwInfo));
|
||||
validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, false);
|
||||
}
|
||||
|
||||
TEST_P(VtsAttestationTests, verifyAttestationWithVeryShortChallengeAndId) {
|
||||
Status result;
|
||||
|
||||
HardwareInformation hwInfo;
|
||||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "c";
|
||||
vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
|
||||
|
@ -131,8 +101,8 @@ TEST_P(VtsAttestationTests, verifyAttestationWithVeryShortChallengeAndId) {
|
|||
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
|
||||
<< endl;
|
||||
|
||||
EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, hwInfo));
|
||||
validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, false);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
|
|
@ -174,16 +174,17 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
|
||||
string cborPretty;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
true /* testCredential */));
|
||||
|
||||
string challenge = "attestationChallenge";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{1} /* atteestationApplicationId */);
|
||||
ASSERT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
EXPECT_TRUE(validateAttestationCertificate(attData.attestationCertificate,
|
||||
attData.attestationChallenge,
|
||||
attData.attestationApplicationId, hwInfo));
|
||||
validateAttestationCertificate(attData.attestationCertificate, attData.attestationChallenge,
|
||||
attData.attestationApplicationId, true);
|
||||
|
||||
// This is kinda of a hack but we need to give the size of
|
||||
// ProofOfProvisioning that we'll expect to receive.
|
||||
|
@ -368,6 +369,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
optional<vector<uint8_t>> signingPubKey =
|
||||
support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
|
||||
EXPECT_TRUE(signingPubKey);
|
||||
test_utils::verifyAuthKeyCertificate(signingKeyCertificate.encodedCertificate);
|
||||
|
||||
// Since we're using a test-credential we know storageKey meaning we can get the
|
||||
// private key. Do this, derive the public key from it, and check this matches what
|
||||
|
@ -418,9 +420,9 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
}
|
||||
|
||||
vector<uint8_t> mac;
|
||||
vector<uint8_t> deviceNameSpacesBytes;
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesBytes).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 32, {});
|
||||
vector<uint8_t> deviceNameSpacesEncoded;
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ(
|
||||
"{\n"
|
||||
" 'PersonalData' : {\n"
|
||||
|
@ -435,37 +437,19 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
" },\n"
|
||||
"}",
|
||||
cborPretty);
|
||||
// The data that is MACed is ["DeviceAuthentication", sessionTranscript, docType,
|
||||
// deviceNameSpacesBytes] so build up that structure
|
||||
cppbor::Array deviceAuthentication;
|
||||
deviceAuthentication.add("DeviceAuthentication");
|
||||
deviceAuthentication.add(sessionTranscript.clone());
|
||||
|
||||
string docType = "org.iso.18013-5.2019.mdl";
|
||||
deviceAuthentication.add(docType);
|
||||
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
|
||||
vector<uint8_t> deviceAuthenticationBytes =
|
||||
cppbor::Semantic(24, deviceAuthentication.encode()).encode();
|
||||
// Derive the key used for MACing.
|
||||
optional<vector<uint8_t>> readerEphemeralPrivateKey =
|
||||
support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
|
||||
optional<vector<uint8_t>> sharedSecret =
|
||||
support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value());
|
||||
ASSERT_TRUE(sharedSecret);
|
||||
// Mix-in SessionTranscriptBytes
|
||||
vector<uint8_t> sessionTranscriptBytes =
|
||||
cppbor::Semantic(24, sessionTranscript.encode()).encode();
|
||||
vector<uint8_t> sharedSecretWithSessionTranscriptBytes = sharedSecret.value();
|
||||
std::copy(sessionTranscriptBytes.begin(), sessionTranscriptBytes.end(),
|
||||
std::back_inserter(sharedSecretWithSessionTranscriptBytes));
|
||||
vector<uint8_t> salt = {0x00};
|
||||
vector<uint8_t> info = {};
|
||||
optional<vector<uint8_t>> derivedKey =
|
||||
support::hkdf(sharedSecretWithSessionTranscriptBytes, salt, info, 32);
|
||||
ASSERT_TRUE(derivedKey);
|
||||
optional<vector<uint8_t>> eMacKey = support::calcEMacKey(
|
||||
readerEphemeralPrivateKey.value(), // Private Key
|
||||
signingPubKey.value(), // Public Key
|
||||
cppbor::Semantic(24, sessionTranscript.encode()).encode()); // SessionTranscriptBytes
|
||||
optional<vector<uint8_t>> calculatedMac =
|
||||
support::coseMac0(derivedKey.value(), {}, // payload
|
||||
deviceAuthenticationBytes); // detached content
|
||||
support::calcMac(sessionTranscript.encode(), // SessionTranscript
|
||||
docType, // DocType
|
||||
deviceNameSpacesEncoded, // DeviceNamespaces
|
||||
eMacKey.value()); // EMacKey
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
|
||||
|
@ -480,18 +464,14 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||
testEntriesEntryCounts)
|
||||
.isOk());
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesBytes).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 32, {});
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ("{}", cborPretty);
|
||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||
deviceAuthentication = cppbor::Array();
|
||||
deviceAuthentication.add("DeviceAuthentication");
|
||||
deviceAuthentication.add(sessionTranscript.clone());
|
||||
deviceAuthentication.add(docType);
|
||||
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
|
||||
deviceAuthenticationBytes = cppbor::Semantic(24, deviceAuthentication.encode()).encode();
|
||||
calculatedMac = support::coseMac0(derivedKey.value(), {}, // payload
|
||||
deviceAuthenticationBytes); // detached content
|
||||
calculatedMac = support::calcMac(sessionTranscript.encode(), // SessionTranscript
|
||||
docType, // DocType
|
||||
deviceNameSpacesEncoded, // DeviceNamespaces
|
||||
eMacKey.value()); // EMacKey
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
|
||||
|
@ -506,18 +486,14 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
|
|||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||
testEntriesEntryCounts)
|
||||
.isOk());
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesBytes).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 32, {});
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ("{}", cborPretty);
|
||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||
deviceAuthentication = cppbor::Array();
|
||||
deviceAuthentication.add("DeviceAuthentication");
|
||||
deviceAuthentication.add(sessionTranscript.clone());
|
||||
deviceAuthentication.add(docType);
|
||||
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
|
||||
deviceAuthenticationBytes = cppbor::Semantic(24, deviceAuthentication.encode()).encode();
|
||||
calculatedMac = support::coseMac0(derivedKey.value(), {}, // payload
|
||||
deviceAuthenticationBytes); // detached content
|
||||
calculatedMac = support::calcMac(sessionTranscript.encode(), // SessionTranscript
|
||||
docType, // DocType
|
||||
deviceNameSpacesEncoded, // DeviceNamespaces
|
||||
eMacKey.value()); // EMacKey
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@ TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
vector<uint8_t> attestationChallenge;
|
||||
vector<Certificate> attestationCertificate;
|
||||
|
@ -82,12 +83,13 @@ TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1";
|
||||
vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
|
||||
vector<Certificate> attestationCertificate;
|
||||
vector<uint8_t> attestationApplicationId = {};
|
||||
vector<uint8_t> attestationApplicationId = {1};
|
||||
|
||||
result = writableCredential->getAttestationCertificate(
|
||||
attestationApplicationId, attestationChallenge, &attestationCertificate);
|
||||
|
@ -95,27 +97,27 @@ TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
|
|||
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
|
||||
<< endl;
|
||||
|
||||
EXPECT_TRUE(test_utils::validateAttestationCertificate(
|
||||
attestationCertificate, attestationChallenge, attestationApplicationId, hwInfo));
|
||||
test_utils::validateAttestationCertificate(attestationCertificate, attestationChallenge,
|
||||
attestationApplicationId, false);
|
||||
}
|
||||
|
||||
TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) {
|
||||
Status result;
|
||||
|
||||
HardwareInformation hwInfo;
|
||||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge1";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
ASSERT_TRUE(test_utils::validateAttestationCertificate(
|
||||
attData.attestationCertificate, attData.attestationChallenge,
|
||||
attData.attestationApplicationId, hwInfo));
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{1} /* atteestationApplicationId */);
|
||||
test_utils::validateAttestationCertificate(attData.attestationCertificate,
|
||||
attData.attestationChallenge,
|
||||
attData.attestationApplicationId, false);
|
||||
|
||||
string challenge2 = "NotSoRandomChallenge2";
|
||||
test_utils::AttestationData attData2(writableCredential, challenge2, {});
|
||||
test_utils::AttestationData attData2(writableCredential, challenge2,
|
||||
{} /* atteestationApplicationId */);
|
||||
EXPECT_FALSE(attData2.result.isOk()) << attData2.result.exceptionCode() << "; "
|
||||
<< attData2.result.exceptionMessage() << endl;
|
||||
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, attData2.result.exceptionCode());
|
||||
|
@ -125,7 +127,8 @@ TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) {
|
|||
TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
// First call should go through
|
||||
const vector<int32_t> entryCounts = {2, 4};
|
||||
|
@ -147,7 +150,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
|
|||
TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
// Verify minimal number of profile count and entry count
|
||||
const vector<int32_t> entryCounts = {1, 1};
|
||||
|
@ -160,7 +164,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
|
|||
TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
// Verify minimal number of profile count and entry count
|
||||
const vector<int32_t> entryCounts = {1};
|
||||
|
@ -173,7 +178,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
|
|||
TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
// Verify set a large number of profile count and entry count is ok
|
||||
const vector<int32_t> entryCounts = {3000};
|
||||
|
@ -186,7 +192,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
|
|||
TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
// Enter mismatched entry and profile numbers
|
||||
const vector<int32_t> entryCounts = {5, 6};
|
||||
|
@ -224,7 +231,8 @@ TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
|
|||
TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
|
||||
Status result;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
const vector<int32_t> entryCounts = {3, 6};
|
||||
writableCredential->setExpectedProofOfProvisioningSize(123456);
|
||||
|
@ -283,10 +291,12 @@ TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge1";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{} /* atteestationApplicationId */);
|
||||
EXPECT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
|
@ -294,7 +304,7 @@ TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
|
|||
ASSERT_TRUE(readerCertificate1);
|
||||
|
||||
const vector<int32_t> entryCounts = {1u};
|
||||
size_t expectedPoPSize = 186 + readerCertificate1.value().size();
|
||||
size_t expectedPoPSize = 185 + readerCertificate1.value().size();
|
||||
// OK to fail, not available in v1 HAL
|
||||
writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
|
||||
result = writableCredential->startPersonalization(1, entryCounts);
|
||||
|
@ -308,7 +318,7 @@ TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
|
|||
ASSERT_TRUE(secureProfiles);
|
||||
|
||||
const vector<test_utils::TestEntryData> testEntries1 = {
|
||||
{"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
|
||||
{"Name Space", "Last name", string("Turing"), vector<int32_t>{1}},
|
||||
};
|
||||
|
||||
map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
|
||||
|
@ -347,11 +357,11 @@ TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
|
|||
" {\n"
|
||||
" 'name' : 'Last name',\n"
|
||||
" 'value' : 'Turing',\n"
|
||||
" 'accessControlProfiles' : [0, 1, ],\n"
|
||||
" 'accessControlProfiles' : [1, ],\n"
|
||||
" },\n"
|
||||
" ],\n"
|
||||
" },\n"
|
||||
" true,\n"
|
||||
" false,\n"
|
||||
"]",
|
||||
cborPretty);
|
||||
|
||||
|
@ -370,10 +380,12 @@ TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{} /* atteestationApplicationId */);
|
||||
EXPECT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
|
@ -510,7 +522,7 @@ TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) {
|
|||
" },\n"
|
||||
" ],\n"
|
||||
" },\n"
|
||||
" true,\n"
|
||||
" false,\n"
|
||||
"]",
|
||||
cborPretty);
|
||||
|
||||
|
@ -529,10 +541,12 @@ TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{} /* atteestationApplicationId */);
|
||||
ASSERT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
|
@ -591,10 +605,12 @@ TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
|
|||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
string challenge = "NotSoRandomChallenge";
|
||||
test_utils::AttestationData attData(writableCredential, challenge, {});
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{} /* atteestationApplicationId */);
|
||||
ASSERT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
|
@ -667,7 +683,8 @@ TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
|
|||
|
||||
TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) {
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
false /* testCredential */));
|
||||
|
||||
const vector<int32_t> entryCounts = {1};
|
||||
writableCredential->setExpectedProofOfProvisioningSize(123456);
|
||||
|
|
|
@ -14,13 +14,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VtsIdentityTestUtils"
|
||||
|
||||
#include "VtsIdentityTestUtils.h"
|
||||
|
||||
#include <aidl/Gtest.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <keymaster/km_openssl/openssl_utils.h>
|
||||
#include <keymasterV4_1/attestation_record.h>
|
||||
#include <charconv>
|
||||
#include <map>
|
||||
|
||||
#include "VtsAttestationParserSupport.h"
|
||||
|
||||
namespace android::hardware::identity::test_utils {
|
||||
|
||||
using std::endl;
|
||||
|
@ -32,15 +36,15 @@ using std::vector;
|
|||
using ::android::sp;
|
||||
using ::android::String16;
|
||||
using ::android::binder::Status;
|
||||
using ::keymaster::X509_Ptr;
|
||||
|
||||
bool setupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
|
||||
sp<IIdentityCredentialStore>& credentialStore) {
|
||||
sp<IIdentityCredentialStore>& credentialStore, bool testCredential) {
|
||||
if (credentialStore == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string docType = "org.iso.18013-5.2019.mdl";
|
||||
bool testCredential = true;
|
||||
Status result = credentialStore->createCredential(docType, testCredential, &writableCredential);
|
||||
|
||||
if (result.isOk() && writableCredential != nullptr) {
|
||||
|
@ -178,63 +182,269 @@ void setImageData(vector<uint8_t>& image) {
|
|||
}
|
||||
}
|
||||
|
||||
bool validateAttestationCertificate(const vector<Certificate>& inputCertificates,
|
||||
const vector<uint8_t>& expectedChallenge,
|
||||
const vector<uint8_t>& expectedAppId,
|
||||
const HardwareInformation& hwInfo) {
|
||||
AttestationCertificateParser certParser_(inputCertificates);
|
||||
bool ret = certParser_.parse();
|
||||
EXPECT_TRUE(ret);
|
||||
if (!ret) {
|
||||
string x509NameToRfc2253String(X509_NAME* name) {
|
||||
char* buf;
|
||||
size_t bufSize;
|
||||
BIO* bio;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253);
|
||||
bufSize = BIO_get_mem_data(bio, &buf);
|
||||
string ret = string(buf, bufSize);
|
||||
BIO_free(bio);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// As per the IC HAL, the version of the Identity
|
||||
// Credential HAL is 1.0 - and this is encoded as major*10 + minor. This field is used by
|
||||
// Keymaster which is known to report integers less than or equal to 4 (for KM up to 4.0)
|
||||
// and integers greater or equal than 41 (for KM starting with 4.1).
|
||||
//
|
||||
// Since we won't get to version 4.0 of the IC HAL for a while, let's also check that a KM
|
||||
// version isn't errornously returned.
|
||||
EXPECT_LE(10, certParser_.getKeymasterVersion());
|
||||
EXPECT_GT(40, certParser_.getKeymasterVersion());
|
||||
EXPECT_LE(3, certParser_.getAttestationVersion());
|
||||
|
||||
// Verify the app id matches to whatever we set it to be.
|
||||
optional<vector<uint8_t>> appId =
|
||||
certParser_.getSwEnforcedBlob(::keymaster::TAG_ATTESTATION_APPLICATION_ID);
|
||||
if (appId) {
|
||||
EXPECT_EQ(expectedAppId.size(), appId.value().size());
|
||||
EXPECT_EQ(0, memcmp(expectedAppId.data(), appId.value().data(), expectedAppId.size()));
|
||||
} else {
|
||||
// app id not found
|
||||
EXPECT_EQ(0, expectedAppId.size());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(certParser_.getHwEnforcedBool(::keymaster::TAG_IDENTITY_CREDENTIAL_KEY));
|
||||
EXPECT_FALSE(certParser_.getHwEnforcedBool(::keymaster::TAG_INCLUDE_UNIQUE_ID));
|
||||
|
||||
// Verify the challenge always matches in size and data of what is passed
|
||||
// in.
|
||||
vector<uint8_t> attChallenge = certParser_.getAttestationChallenge();
|
||||
EXPECT_EQ(expectedChallenge.size(), attChallenge.size());
|
||||
EXPECT_EQ(0, memcmp(expectedChallenge.data(), attChallenge.data(), expectedChallenge.size()));
|
||||
|
||||
// Ensure the attestation conveys that it's implemented in secure hardware (with carve-out
|
||||
// for the reference implementation which cannot be implemented in secure hardware).
|
||||
if (hwInfo.credentialStoreName == "Identity Credential Reference Implementation" &&
|
||||
hwInfo.credentialStoreAuthorName == "Google") {
|
||||
EXPECT_LE(KM_SECURITY_LEVEL_SOFTWARE, certParser_.getKeymasterSecurityLevel());
|
||||
EXPECT_LE(KM_SECURITY_LEVEL_SOFTWARE, certParser_.getAttestationSecurityLevel());
|
||||
|
||||
} else {
|
||||
// Actual devices should use TrustedEnvironment or StrongBox.
|
||||
EXPECT_LE(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT, certParser_.getKeymasterSecurityLevel());
|
||||
EXPECT_LE(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT, certParser_.getAttestationSecurityLevel());
|
||||
time_t t = timegm(&tm);
|
||||
if (t == -1) {
|
||||
LOG(ERROR) << "Error converting broken-down time to time_t";
|
||||
return false;
|
||||
}
|
||||
*outTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
void validateAttestationCertificate(const vector<Certificate>& credentialKeyCertChain,
|
||||
const vector<uint8_t>& expectedChallenge,
|
||||
const vector<uint8_t>& expectedAppId, bool isTestCredential) {
|
||||
ASSERT_GE(credentialKeyCertChain.size(), 2);
|
||||
|
||||
vector<uint8_t> certBytes = credentialKeyCertChain[0].encodedCertificate;
|
||||
const uint8_t* certData = certBytes.data();
|
||||
X509_Ptr cert = X509_Ptr(d2i_X509(nullptr, &certData, certBytes.size()));
|
||||
|
||||
vector<uint8_t> batchCertBytes = credentialKeyCertChain[1].encodedCertificate;
|
||||
const uint8_t* batchCertData = batchCertBytes.data();
|
||||
X509_Ptr batchCert = X509_Ptr(d2i_X509(nullptr, &batchCertData, batchCertBytes.size()));
|
||||
|
||||
// First get some values from the batch certificate which is checked
|
||||
// against the top-level certificate (subject, notAfter)
|
||||
//
|
||||
|
||||
X509_NAME* batchSubject = X509_get_subject_name(batchCert.get());
|
||||
ASSERT_NE(nullptr, batchSubject);
|
||||
time_t batchNotAfter;
|
||||
ASSERT_TRUE(parseAsn1Time(X509_get0_notAfter(batchCert.get()), &batchNotAfter));
|
||||
|
||||
// Check all the requirements from IWritableIdentityCredential::getAttestationCertificate()...
|
||||
//
|
||||
|
||||
// - version: INTEGER 2 (means v3 certificate).
|
||||
EXPECT_EQ(2, X509_get_version(cert.get()));
|
||||
|
||||
// - serialNumber: INTEGER 1 (fixed value: same on all certs).
|
||||
EXPECT_EQ(1, ASN1_INTEGER_get(X509_get_serialNumber(cert.get())));
|
||||
|
||||
// - signature: must be set to ECDSA.
|
||||
EXPECT_EQ(NID_ecdsa_with_SHA256, X509_get_signature_nid(cert.get()));
|
||||
|
||||
// - subject: CN shall be set to "Android Identity Credential Key". (fixed value:
|
||||
// same on all certs)
|
||||
X509_NAME* subject = X509_get_subject_name(cert.get());
|
||||
ASSERT_NE(nullptr, subject);
|
||||
EXPECT_EQ("CN=Android Identity Credential Key", x509NameToRfc2253String(subject));
|
||||
|
||||
// - issuer: Same as the subject field of the batch attestation key.
|
||||
X509_NAME* issuer = X509_get_issuer_name(cert.get());
|
||||
ASSERT_NE(nullptr, issuer);
|
||||
EXPECT_EQ(x509NameToRfc2253String(batchSubject), x509NameToRfc2253String(issuer));
|
||||
|
||||
// - validity: Should be from current time and expire at the same time as the
|
||||
// attestation batch certificate used.
|
||||
//
|
||||
// Allow for 10 seconds drift to account for the time drift between Secure HW
|
||||
// and this environment plus the difference between when the certificate was
|
||||
// created and until now
|
||||
//
|
||||
time_t notBefore;
|
||||
ASSERT_TRUE(parseAsn1Time(X509_get0_notBefore(cert.get()), ¬Before));
|
||||
uint64_t now = time(nullptr);
|
||||
int64_t diffSecs = now - notBefore;
|
||||
int64_t allowDriftSecs = 10;
|
||||
EXPECT_LE(-allowDriftSecs, diffSecs);
|
||||
EXPECT_GE(allowDriftSecs, diffSecs);
|
||||
|
||||
time_t notAfter;
|
||||
ASSERT_TRUE(parseAsn1Time(X509_get0_notAfter(cert.get()), ¬After));
|
||||
EXPECT_EQ(notAfter, batchNotAfter);
|
||||
|
||||
auto [err, attRec] = keymaster::V4_1::parse_attestation_record(certBytes);
|
||||
ASSERT_EQ(keymaster::V4_1::ErrorCode::OK, err);
|
||||
|
||||
// - subjectPublicKeyInfo: must contain attested public key.
|
||||
|
||||
// - The attestationVersion field in the attestation extension must be at least 3.
|
||||
EXPECT_GE(attRec.attestation_version, 3);
|
||||
|
||||
// - The attestationSecurityLevel field must be set to either Software (0),
|
||||
// TrustedEnvironment (1), or StrongBox (2) depending on how attestation is
|
||||
// implemented.
|
||||
EXPECT_GE(attRec.attestation_security_level,
|
||||
keymaster::V4_0::SecurityLevel::TRUSTED_ENVIRONMENT);
|
||||
|
||||
// - The keymasterVersion field in the attestation extension must be set to the.
|
||||
// same value as used for Android Keystore keys.
|
||||
//
|
||||
// Nothing to check here...
|
||||
|
||||
// - The keymasterSecurityLevel field in the attestation extension must be set to
|
||||
// either Software (0), TrustedEnvironment (1), or StrongBox (2) depending on how
|
||||
// the Trusted Application backing the HAL implementation is implemented.
|
||||
EXPECT_GE(attRec.keymaster_security_level, keymaster::V4_0::SecurityLevel::TRUSTED_ENVIRONMENT);
|
||||
|
||||
// - The attestationChallenge field must be set to the passed-in challenge.
|
||||
EXPECT_EQ(expectedChallenge.size(), attRec.attestation_challenge.size());
|
||||
EXPECT_TRUE(memcmp(expectedChallenge.data(), attRec.attestation_challenge.data(),
|
||||
attRec.attestation_challenge.size()) == 0);
|
||||
|
||||
// - The uniqueId field must be empty.
|
||||
EXPECT_EQ(attRec.unique_id.size(), 0);
|
||||
|
||||
// - The softwareEnforced field in the attestation extension must include
|
||||
// Tag::ATTESTATION_APPLICATION_ID which must be set to the bytes of the passed-in
|
||||
// attestationApplicationId.
|
||||
EXPECT_TRUE(attRec.software_enforced.Contains(keymaster::V4_0::TAG_ATTESTATION_APPLICATION_ID,
|
||||
expectedAppId));
|
||||
|
||||
// - The teeEnforced field in the attestation extension must include
|
||||
//
|
||||
// - Tag::IDENTITY_CREDENTIAL_KEY which indicates that the key is an Identity
|
||||
// Credential key (which can only sign/MAC very specific messages) and not an Android
|
||||
// Keystore key (which can be used to sign/MAC anything). This must not be set
|
||||
// for test credentials.
|
||||
bool hasIcKeyTag =
|
||||
attRec.hardware_enforced.Contains(static_cast<android::hardware::keymaster::V4_0::Tag>(
|
||||
keymaster::V4_1::Tag::IDENTITY_CREDENTIAL_KEY));
|
||||
if (isTestCredential) {
|
||||
EXPECT_FALSE(hasIcKeyTag);
|
||||
} else {
|
||||
EXPECT_TRUE(hasIcKeyTag);
|
||||
}
|
||||
|
||||
// - Tag::PURPOSE must be set to SIGN
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_PURPOSE,
|
||||
keymaster::V4_0::KeyPurpose::SIGN));
|
||||
|
||||
// - Tag::KEY_SIZE must be set to the appropriate key size, in bits (e.g. 256)
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_KEY_SIZE, 256));
|
||||
|
||||
// - Tag::ALGORITHM must be set to EC
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_ALGORITHM,
|
||||
keymaster::V4_0::Algorithm::EC));
|
||||
|
||||
// - Tag::NO_AUTH_REQUIRED must be set
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_NO_AUTH_REQUIRED));
|
||||
|
||||
// - Tag::DIGEST must be include SHA_2_256
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_DIGEST,
|
||||
keymaster::V4_0::Digest::SHA_2_256));
|
||||
|
||||
// - Tag::EC_CURVE must be set to P_256
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_EC_CURVE,
|
||||
keymaster::V4_0::EcCurve::P_256));
|
||||
|
||||
// - Tag::ROOT_OF_TRUST must be set
|
||||
//
|
||||
EXPECT_GE(attRec.root_of_trust.security_level,
|
||||
keymaster::V4_0::SecurityLevel::TRUSTED_ENVIRONMENT);
|
||||
|
||||
// - Tag::OS_VERSION and Tag::OS_PATCHLEVEL must be set
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_OS_VERSION));
|
||||
EXPECT_TRUE(attRec.hardware_enforced.Contains(keymaster::V4_0::TAG_OS_PATCHLEVEL));
|
||||
|
||||
// TODO: we could retrieve osVersion and osPatchLevel from Android itself and compare it
|
||||
// with what was reported in the certificate.
|
||||
}
|
||||
|
||||
void verifyAuthKeyCertificate(const vector<uint8_t>& authKeyCertChain) {
|
||||
const uint8_t* data = authKeyCertChain.data();
|
||||
auto cert = X509_Ptr(d2i_X509(nullptr, &data, authKeyCertChain.size()));
|
||||
|
||||
// - version: INTEGER 2 (means v3 certificate).
|
||||
EXPECT_EQ(X509_get_version(cert.get()), 2);
|
||||
|
||||
// - serialNumber: INTEGER 1 (fixed value: same on all certs).
|
||||
EXPECT_EQ(ASN1_INTEGER_get(X509_get_serialNumber(cert.get())), 1);
|
||||
|
||||
// - signature: must be set to ECDSA.
|
||||
EXPECT_EQ(X509_get_signature_nid(cert.get()), NID_ecdsa_with_SHA256);
|
||||
|
||||
// - subject: CN shall be set to "Android Identity Credential Authentication Key". (fixed
|
||||
// value: same on all certs)
|
||||
X509_NAME* subject = X509_get_subject_name(cert.get());
|
||||
ASSERT_NE(subject, nullptr);
|
||||
EXPECT_EQ(x509NameToRfc2253String(subject),
|
||||
"CN=Android Identity Credential Authentication Key");
|
||||
|
||||
// - issuer: CN shall be set to "Android Identity Credential Key". (fixed value:
|
||||
// same on all certs)
|
||||
X509_NAME* issuer = X509_get_issuer_name(cert.get());
|
||||
ASSERT_NE(issuer, nullptr);
|
||||
EXPECT_EQ(x509NameToRfc2253String(issuer), "CN=Android Identity Credential Key");
|
||||
|
||||
// - subjectPublicKeyInfo: must contain attested public key.
|
||||
|
||||
// - validity: should be from current time and one year in the future (365 days).
|
||||
time_t notBefore, notAfter;
|
||||
ASSERT_TRUE(parseAsn1Time(X509_get0_notAfter(cert.get()), ¬After));
|
||||
ASSERT_TRUE(parseAsn1Time(X509_get0_notBefore(cert.get()), ¬Before));
|
||||
|
||||
// Allow for 10 seconds drift to account for the time drift between Secure HW
|
||||
// and this environment plus the difference between when the certificate was
|
||||
// created and until now
|
||||
//
|
||||
uint64_t now = time(nullptr);
|
||||
int64_t diffSecs = now - notBefore;
|
||||
int64_t allowDriftSecs = 10;
|
||||
EXPECT_LE(-allowDriftSecs, diffSecs);
|
||||
EXPECT_GE(allowDriftSecs, diffSecs);
|
||||
constexpr uint64_t kSecsInOneYear = 365 * 24 * 60 * 60;
|
||||
EXPECT_EQ(notBefore + kSecsInOneYear, notAfter);
|
||||
}
|
||||
|
||||
vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries) {
|
||||
vector<RequestNamespace> ret;
|
||||
RequestNamespace curNs;
|
||||
|
|
|
@ -34,8 +34,8 @@ using ::android::binder::Status;
|
|||
|
||||
struct AttestationData {
|
||||
AttestationData(sp<IWritableIdentityCredential>& writableCredential, string challenge,
|
||||
vector<uint8_t> applicationId)
|
||||
: attestationApplicationId(applicationId) {
|
||||
vector<uint8_t> attestationAppId)
|
||||
: attestationApplicationId(attestationAppId) {
|
||||
// ASSERT_NE(writableCredential, nullptr);
|
||||
|
||||
if (!challenge.empty()) {
|
||||
|
@ -94,7 +94,7 @@ struct TestProfile {
|
|||
};
|
||||
|
||||
bool setupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
|
||||
sp<IIdentityCredentialStore>& credentialStore);
|
||||
sp<IIdentityCredentialStore>& credentialStore, bool testCredential);
|
||||
|
||||
optional<vector<uint8_t>> generateReaderCertificate(string serialDecimal);
|
||||
|
||||
|
@ -111,13 +111,17 @@ bool addEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEnt
|
|||
|
||||
void setImageData(vector<uint8_t>& image);
|
||||
|
||||
bool validateAttestationCertificate(const vector<Certificate>& inputCertificates,
|
||||
void validateAttestationCertificate(const vector<Certificate>& credentialKeyCertChain,
|
||||
const vector<uint8_t>& expectedChallenge,
|
||||
const vector<uint8_t>& expectedAppId,
|
||||
const HardwareInformation& hwInfo);
|
||||
const vector<uint8_t>& expectedAppId, bool isTestCredential);
|
||||
|
||||
vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries);
|
||||
|
||||
// Verifies that the X.509 certificate for a just created authentication key
|
||||
// is valid.
|
||||
//
|
||||
void verifyAuthKeyCertificate(const vector<uint8_t>& authKeyCertChain);
|
||||
|
||||
} // namespace android::hardware::identity::test_utils
|
||||
|
||||
#endif // VTS_IDENTITY_TEST_UTILS_H
|
||||
|
|
|
@ -35,6 +35,9 @@ using ::std::tuple;
|
|||
using ::std::vector;
|
||||
using ::std::pair;
|
||||
|
||||
// The semantic tag for a bstr which includes Encoded CBOR (RFC 7049, section 2.4)
|
||||
const int kSemanticTagEncodedCbor = 24;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Miscellaneous utilities.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -108,45 +111,47 @@ optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vec
|
|||
// ---------------------------------------------------------------------------
|
||||
// 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.
|
||||
// DER encoded private key. Also generates an attestation using the |challenge|
|
||||
// and |applicationId|, and returns the generated certificate chain.
|
||||
//
|
||||
// The attestation time fields used will be the current time, and expires in one year.
|
||||
// The notBeffore field will be the current time and the notAfter will be the same
|
||||
// same time as the batch certificate.
|
||||
//
|
||||
// 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);
|
||||
|
||||
// Like createEcKeyPairAndAttestation() but allows you to choose the public key.
|
||||
//
|
||||
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAndAttestation(
|
||||
const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId,
|
||||
bool isTestCredential);
|
||||
|
||||
// (TODO: remove when no longer used by 3rd party.)
|
||||
optional<vector<vector<uint8_t>>> createAttestationForEcPublicKey(
|
||||
const vector<uint8_t>& publicKey, 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.
|
||||
// private key in DER format (as specified in RFC 5915).
|
||||
//
|
||||
optional<vector<uint8_t>> createEcKeyPair();
|
||||
|
||||
// For an EC key |keyPair| encoded in PKCS#8 format, extracts the public key in
|
||||
// For an EC key |keyPair| encoded in DER format, extracts the public key in
|
||||
// uncompressed point form.
|
||||
//
|
||||
optional<vector<uint8_t>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair);
|
||||
|
||||
// For an EC key |keyPair| encoded in PKCS#8 format, extracts the private key as
|
||||
// For an EC key |keyPair| encoded in DER format, extracts the private key as
|
||||
// an EC uncompressed key.
|
||||
//
|
||||
optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair);
|
||||
|
||||
// Creates a PKCS#8 encoded key-pair from a private key (which must be uncompressed,
|
||||
// e.g. 32 bytes). The public key is derived from the given private key..
|
||||
// Creates a DER encoded representation from a private key (which must be uncompressed,
|
||||
// e.g. 32 bytes).
|
||||
//
|
||||
optional<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& privateKey);
|
||||
|
||||
// For an EC key |keyPair| encoded in PKCS#8 format, creates a PKCS#12 structure
|
||||
// For an EC key |keyPair| encoded in DER format, creates a PKCS#12 structure
|
||||
// with the key-pair (not using a password to encrypt the data). The public key
|
||||
// in the created structure is included as a certificate, using the given fields
|
||||
// |serialDecimal|, |issuer|, |subject|, |validityNotBefore|, and
|
||||
|
@ -209,6 +214,13 @@ optional<pair<size_t, size_t>> certificateTbsCertificate(const vector<uint8_t>&
|
|||
//
|
||||
optional<pair<size_t, size_t>> certificateFindSignature(const vector<uint8_t>& x509Certificate);
|
||||
|
||||
// Extracts notBefore and notAfter from the top-most certificate in |certificateChain
|
||||
// (which should be a concatenated chain of DER-encoded X.509 certificates).
|
||||
//
|
||||
// Returns notBefore and notAfter in that order.
|
||||
//
|
||||
optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x509Certificate);
|
||||
|
||||
// Generates a X.509 certificate for |publicKey| (which must be in the format
|
||||
// returned by ecKeyPairGetPublicKey()).
|
||||
//
|
||||
|
@ -351,6 +363,15 @@ optional<vector<uint8_t>> coseMacWithDigest(const vector<uint8_t>& digestToBeMac
|
|||
// Utility functions specific to IdentityCredential.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
optional<vector<uint8_t>> calcMac(const vector<uint8_t>& sessionTranscriptEncoded,
|
||||
const string& docType,
|
||||
const vector<uint8_t>& deviceNameSpacesEncoded,
|
||||
const vector<uint8_t>& eMacKey);
|
||||
|
||||
optional<vector<uint8_t>> calcEMacKey(const vector<uint8_t>& privateKey,
|
||||
const vector<uint8_t>& publicKey,
|
||||
const vector<uint8_t>& sessionTranscriptBytes);
|
||||
|
||||
// Returns the testing AES-128 key where all bits are set to 0.
|
||||
const vector<uint8_t>& getTestHardwareBoundKey();
|
||||
|
||||
|
|
|
@ -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 @@ optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<ui
|
|||
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 @@ optional<vector<vector<uint8_t>>> createAttestation(const EVP_PKEY* key,
|
|||
::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 @@ optional<vector<vector<uint8_t>>> createAttestation(const EVP_PKEY* key,
|
|||
.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 {};
|
||||
}
|
||||
|
||||
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 {};
|
||||
// Only include TAG_IDENTITY_CREDENTIAL_KEY if it's not a test credential
|
||||
if (!isTestCredential) {
|
||||
hwEnforcedBuilder.Authorization(::keymaster::TAG_IDENTITY_CREDENTIAL_KEY);
|
||||
}
|
||||
::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;
|
||||
// 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(key, swEnforced, hwEnforced, auth_set, context,
|
||||
identity_version, *attestation_chain,
|
||||
*attestation_signing_key, &cert_chain_out);
|
||||
::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<vector<vector<uint8_t>>> createAttestation(const EVP_PKEY* key,
|
|||
}
|
||||
|
||||
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 @@ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAnd
|
|||
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 @@ optional<vector<vector<uint8_t>>> createAttestationForEcPublicKey(
|
|||
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 {};
|
||||
|
@ -1652,6 +1727,32 @@ optional<pair<size_t, size_t>> certificateTbsCertificate(const vector<uint8_t>&
|
|||
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)) {
|
||||
|
@ -2224,6 +2325,49 @@ optional<vector<uint8_t>> coseMacWithDigest(const vector<uint8_t>& digestToBeMac
|
|||
// 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;
|
||||
|
||||
|
|
|
@ -436,6 +436,300 @@ TEST(IdentityCredentialSupport, CoseMac0DetachedContent) {
|
|||
support::cborPrettyPrint(mac.value()));
|
||||
}
|
||||
|
||||
// Generates a private key in DER format for a small value of 'd'.
|
||||
//
|
||||
// Used for test vectors.
|
||||
//
|
||||
vector<uint8_t> p256PrivateKeyFromD(uint8_t d) {
|
||||
vector<uint8_t> privateUncompressed;
|
||||
privateUncompressed.resize(32);
|
||||
privateUncompressed[31] = d;
|
||||
optional<vector<uint8_t>> privateKey = support::ecPrivateKeyToKeyPair(privateUncompressed);
|
||||
return privateKey.value();
|
||||
}
|
||||
|
||||
std::pair<vector<uint8_t>, vector<uint8_t>> p256PrivateKeyGetXandY(
|
||||
const vector<uint8_t> privateKey) {
|
||||
optional<vector<uint8_t>> publicUncompressed = support::ecKeyPairGetPublicKey(privateKey);
|
||||
vector<uint8_t> x = vector<uint8_t>(publicUncompressed.value().begin() + 1,
|
||||
publicUncompressed.value().begin() + 33);
|
||||
vector<uint8_t> y = vector<uint8_t>(publicUncompressed.value().begin() + 33,
|
||||
publicUncompressed.value().begin() + 65);
|
||||
return std::make_pair(x, y);
|
||||
}
|
||||
|
||||
const cppbor::Item* findValueForTstr(const cppbor::Map* map, const string& keyValue) {
|
||||
// TODO: Need cast until libcppbor's Map::get() is marked as const
|
||||
auto [item, found] = ((cppbor::Map*)map)->get(keyValue);
|
||||
if (!found) {
|
||||
return nullptr;
|
||||
}
|
||||
return item.get();
|
||||
}
|
||||
|
||||
const cppbor::Array* findArrayValueForTstr(const cppbor::Map* map, const string& keyValue) {
|
||||
const cppbor::Item* item = findValueForTstr(map, keyValue);
|
||||
if (item == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return item->asArray();
|
||||
}
|
||||
|
||||
const cppbor::Map* findMapValueForTstr(const cppbor::Map* map, const string& keyValue) {
|
||||
const cppbor::Item* item = findValueForTstr(map, keyValue);
|
||||
if (item == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return item->asMap();
|
||||
}
|
||||
|
||||
const cppbor::Semantic* findSemanticValueForTstr(const cppbor::Map* map, const string& keyValue) {
|
||||
const cppbor::Item* item = findValueForTstr(map, keyValue);
|
||||
if (item == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return item->asSemantic();
|
||||
}
|
||||
|
||||
const std::string findStringValueForTstr(const cppbor::Map* map, const string& keyValue) {
|
||||
const cppbor::Item* item = findValueForTstr(map, keyValue);
|
||||
if (item == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const cppbor::Tstr* tstr = item->asTstr();
|
||||
if (tstr == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return tstr->value();
|
||||
}
|
||||
|
||||
TEST(IdentityCredentialSupport, testVectors_18013_5) {
|
||||
// This is a test against known vectors for ISO 18013-5.
|
||||
//
|
||||
// The objective of this test is to verify that support::calcEMacKey() and
|
||||
// support::calcMac() agree with the given test vectors.
|
||||
//
|
||||
|
||||
// We're given static device key:
|
||||
//
|
||||
// x: 28412803729898893058558238221310261427084375743576167377786533380249859400145
|
||||
// y: 65403602826180996396520286939226973026599920614829401631985882360676038096704
|
||||
// d: 11
|
||||
//
|
||||
vector<uint8_t> deviceKey = p256PrivateKeyFromD(11);
|
||||
auto [deviceKeyX, deviceKeyY] = p256PrivateKeyGetXandY(deviceKey);
|
||||
EXPECT_EQ(support::encodeHex(deviceKeyX),
|
||||
"3ed113b7883b4c590638379db0c21cda16742ed0255048bf433391d374bc21d1");
|
||||
EXPECT_EQ(support::encodeHex(deviceKeyY),
|
||||
"9099209accc4c8a224c843afa4f4c68a090d04da5e9889dae2f8eefce82a3740");
|
||||
|
||||
// We're given Ephemeral reader key:
|
||||
//
|
||||
// x: 59535862115950685744176693329402396749019581632805653266809849538337418304154
|
||||
// y: 53776829996815113213100700404832701936765102413212294632483274374518863708344
|
||||
// d: 20
|
||||
//
|
||||
vector<uint8_t> ephemeralReaderKey = p256PrivateKeyFromD(20);
|
||||
auto [ephemeralReaderKeyX, ephemeralReaderKeyY] = p256PrivateKeyGetXandY(ephemeralReaderKey);
|
||||
EXPECT_EQ(support::encodeHex(ephemeralReaderKeyX),
|
||||
"83a01a9378395bab9bcd6a0ad03cc56d56e6b19250465a94a234dc4c6b28da9a");
|
||||
EXPECT_EQ(support::encodeHex(ephemeralReaderKeyY),
|
||||
"76e49b6de2f73234ae6a5eb9d612b75c9f2202bb6923f54ff8240aaa86f640b8");
|
||||
vector<uint8_t> ephemeralReaderKeyPublic =
|
||||
support::ecKeyPairGetPublicKey(ephemeralReaderKey).value();
|
||||
|
||||
// We're given SessionEstablishment.
|
||||
//
|
||||
// SessionEstablishment = {
|
||||
// "eReaderKey" : EReaderKeyBytes,
|
||||
// "data" : bstr ; Encrypted mdoc request
|
||||
// }
|
||||
//
|
||||
// Fish out EReaderKey from this.
|
||||
//
|
||||
// Note that the test vector below is incorrect insofar that it uses
|
||||
// "eReaderKeyBytes" instead of just "eReaderKey". This will be corrected in
|
||||
// the future.
|
||||
//
|
||||
optional<vector<uint8_t>> sessionEstablishmentEncoded = support::decodeHex(
|
||||
"a26f655265616465724b65794279746573d818584ba40102200121582083a01a9378395bab9bcd6a0ad03c"
|
||||
"c56d56e6b19250465a94a234dc4c6b28da9a22582076e49b6de2f73234ae6a5eb9d612b75c9f2202bb6923"
|
||||
"f54ff8240aaa86f640b864646174615902d945b31040c57491acb6d46a71f6c1f67a0b837df1bda9089fd0"
|
||||
"3d0b1fdac3eeb2874a4ef6f90c97d03397186ba00a91102faae7e992e15f761d5662c3c37e3c6c2cfd2ebc"
|
||||
"0bf59dbb8795e377bd7dd353230a41ba2d82294b45871a39b42ca531f26b52f46e356fbaf5075c8fd5b8b0"
|
||||
"8a0df4a1d2e1bdd2e5d69169c1efbb51e393e608d833d325bebfbccb2e15ec08f94b264582fa7b93f7cebc"
|
||||
"aa69f4f0cac2744d4fe35b04df26b2ae69273eed33024949080c1c95a6ef046beede959e9494297dd770af"
|
||||
"4ac6fdd56783aa012555c213dc05cf0f41d1c95119720fcfe1621027f80e2ddd56ea3c1fc596f7b2579333"
|
||||
"5a887ec788092b4a69d23b6219e27d0249b50b3fdcb95b5227007689362e0416b3bae3dae7cb56b4394666"
|
||||
"4e3a3f60dce8d0b678fcd754bebf87bd2b0278dd782d952488a46f2874e34c2dd97bb74084a62b850e9719"
|
||||
"252cd1dca7dbf1858193f6cf093cb3735312bbe1138cf29d8f350e285923f8ef07065299926720b42264e8"
|
||||
"fd5d4b133e72f47c4e999ea689c353f8b41e50a59838e1a0d09eca4a557f77a9c389a0591ad1639119ce86"
|
||||
"edc3320130480ee5101effae6066e8c85aac9ead2ae83e49c1e508aab02f753decbb522ea2200d62fd5d26"
|
||||
"094bd35100bffaa1cdc6af9f7e9cfe7b63da6b5671cd5ac2cf5da450c72addc64cde441f3b7f7fdaf930ad"
|
||||
"1e13388e8a7308d8ca4607e59e082db431a232e7e12cb692baeb4b2127e110ff24cea322ffdbc2e4d9c4c6"
|
||||
"bed27753137d07897c8613627a799a560cf1a2d1edb3de029442862940a5ed7785eea8b6ace93aa6af0792"
|
||||
"fd82877f62d07b757d0179ecbb7347004ecc9c0690d41f75f188cb17ffd2cec2ad8c9675466bb33b737a2a"
|
||||
"e7592b2dcb8132aced2e572266f3f5413a5f9d6d4339a1e4662622af2e7e157a4ea3bfd5c4247e2ec91d8c"
|
||||
"5c3c17427d5edfae673d0e0f782a8d40fa805fd8bc82ae3cb21a65cdad863e02309f6b01d1753fa884b778"
|
||||
"f6e019a2004d8964deeb11f1fd478fcb");
|
||||
ASSERT_TRUE(sessionEstablishmentEncoded);
|
||||
auto [sessionEstablishmentItem, _se, _se2] = cppbor::parse(sessionEstablishmentEncoded.value());
|
||||
const cppbor::Map* sessionEstablishment = sessionEstablishmentItem->asMap();
|
||||
ASSERT_NE(sessionEstablishment, nullptr);
|
||||
const cppbor::Semantic* eReaderKeyBytes =
|
||||
findSemanticValueForTstr(sessionEstablishment, "eReaderKeyBytes");
|
||||
ASSERT_NE(eReaderKeyBytes, nullptr);
|
||||
ASSERT_EQ(eReaderKeyBytes->value(), 24);
|
||||
const cppbor::Bstr* eReaderKeyBstr = eReaderKeyBytes->child()->asBstr();
|
||||
ASSERT_NE(eReaderKeyBstr, nullptr);
|
||||
vector<uint8_t> eReaderKeyEncoded = eReaderKeyBstr->value();
|
||||
// TODO: verify this agrees with ephemeralReaderKeyX and ephemeralReaderKeyY
|
||||
|
||||
// We're given DeviceEngagement.
|
||||
//
|
||||
vector<uint8_t> deviceEngagementEncoded =
|
||||
support::decodeHex(
|
||||
"a20063312e30018201d818584ba401022001215820cef66d6b2a3a993e591214d1ea223fb545ca"
|
||||
"6c471c48306e4c36069404c5723f225820878662a229aaae906e123cdd9d3b4c10590ded29fe75"
|
||||
"1eeeca34bbaa44af0773")
|
||||
.value();
|
||||
|
||||
// Now calculate SessionTranscriptBytes. It is defined as
|
||||
//
|
||||
// SessionTranscript = [
|
||||
// DeviceEngagementBytes,
|
||||
// EReaderKeyBytes,
|
||||
// Handover
|
||||
// ]
|
||||
//
|
||||
// SessionTranscriptBytes = #6.24(bstr .cbor SessionTranscript)
|
||||
//
|
||||
cppbor::Array sessionTranscript;
|
||||
sessionTranscript.add(cppbor::Semantic(24, deviceEngagementEncoded));
|
||||
sessionTranscript.add(cppbor::Semantic(24, eReaderKeyEncoded));
|
||||
sessionTranscript.add(cppbor::Null());
|
||||
vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode();
|
||||
vector<uint8_t> sessionTranscriptBytes =
|
||||
cppbor::Semantic(24, sessionTranscriptEncoded).encode();
|
||||
|
||||
// The expected EMacKey is 4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2
|
||||
//
|
||||
// Verify that support::calcEMacKey() gets the same result.
|
||||
//
|
||||
optional<vector<uint8_t>> eMacKey =
|
||||
support::calcEMacKey(support::ecKeyPairGetPrivateKey(deviceKey).value(), // private key
|
||||
ephemeralReaderKeyPublic, // public key
|
||||
sessionTranscriptBytes); // sessionTranscriptBytes
|
||||
ASSERT_TRUE(eMacKey);
|
||||
ASSERT_EQ(support::encodeHex(eMacKey.value()),
|
||||
"4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2");
|
||||
|
||||
// Also do it the other way around
|
||||
//
|
||||
optional<vector<uint8_t>> eMacKey2 = support::calcEMacKey(
|
||||
support::ecKeyPairGetPrivateKey(ephemeralReaderKey).value(), // private key
|
||||
support::ecKeyPairGetPublicKey(deviceKey).value(), // public key
|
||||
sessionTranscriptBytes); // sessionTranscriptBytes
|
||||
ASSERT_TRUE(eMacKey2);
|
||||
ASSERT_EQ(support::encodeHex(eMacKey2.value()),
|
||||
"4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2");
|
||||
|
||||
// We're given DeviceResponse
|
||||
//
|
||||
vector<uint8_t> deviceResponseEncoded =
|
||||
support::decodeHex(
|
||||
"a36776657273696f6e63312e3069646f63756d656e747381a367646f6354797065756f72672e69"
|
||||
"736f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d6553706163"
|
||||
"6573a2716f72672e69736f2e31383031332e352e3181d8185863a4686469676573744944016672"
|
||||
"616e646f6d58208798645b20ea200e19ffabac92624bee6aec63aceedecfb1b80077d22bfc20e9"
|
||||
"71656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456"
|
||||
"616c756563446f656b636f6d2e6578616d706c6581d8185864a468646967657374494401667261"
|
||||
"6e646f6d5820218ecf13521b53f4b96abaebe56417afec0e4c91fc8fb26086cd1e5cdc1a94ff71"
|
||||
"656c656d656e744964656e7469666965726f616e6f746865725f656c656d656e746c656c656d65"
|
||||
"6e7456616c75650a6a697373756572417574688443a10126a118215901d2308201ce30820174a0"
|
||||
"0302010202141f7d44f4f107c5ee3f566049cf5d72de294b0d23300a06082a8648ce3d04030230"
|
||||
"233114301206035504030c0b75746f7069612069616361310b3009060355040613025553301e17"
|
||||
"0d3230313030313030303030305a170d3231313030313030303030305a30213112301006035504"
|
||||
"030c0975746f706961206473310b30090603550406130255533059301306072a8648ce3d020106"
|
||||
"082a8648ce3d03010703420004301d9e502dc7e05da85da026a7ae9aa0fac9db7d52a95b3e3e3f"
|
||||
"9aa0a1b45b8b6551b6f6b3061223e0d23c026b017d72298d9ae46887ca61d58db6aea17ee267a3"
|
||||
"8187308184301e0603551d120417301581136578616d706c65406578616d706c652e636f6d301c"
|
||||
"0603551d1f041530133011a00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414"
|
||||
"7bef4db59a1ffb07592bfc57f4743b8a73aea792300e0603551d0f0101ff040403020780301506"
|
||||
"03551d250101ff040b3009060728818c5d050102300a06082a8648ce3d04030203480030450220"
|
||||
"21d52fb1fbda80e5bfda1e8dfb1bc7bf0acb7261d5c9ff54425af76eb21571c602210082bf301f"
|
||||
"89e0a2cb9ca9c9050352de80b47956764f7a3e07bf6a8cd87528a3b55901d2d8185901cda66776"
|
||||
"657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c76616c75"
|
||||
"6544696765737473a2716f72672e69736f2e31383031332e352e31a20058203b22af1126771f02"
|
||||
"f0ea0d546d4ee3c5b51637381154f5211b79daf5f9facaa8015820f2cba0ce3cde5df901a3da75"
|
||||
"13a4d7f7225fdfe5a306544529bf3dbcce655ca06b636f6d2e6578616d706c65a200582072636d"
|
||||
"ddc282424a63499f4b3927aaa3b74da7b9c0134178bf735e949e4a761e01582006322d3cbe6603"
|
||||
"876bdacc5b6679b51b0fc53d029c244fd5ea719d9028459c916d6465766963654b6579496e666f"
|
||||
"a1696465766963654b6579a4010220012158203ed113b7883b4c590638379db0c21cda16742ed0"
|
||||
"255048bf433391d374bc21d12258209099209accc4c8a224c843afa4f4c68a090d04da5e9889da"
|
||||
"e2f8eefce82a374067646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c"
|
||||
"76616c6964697479496e666fa3667369676e6564c074323032302d31302d30315431333a33303a"
|
||||
"30325a6976616c696446726f6dc074323032302d31302d30315431333a33303a30325a6a76616c"
|
||||
"6964556e74696cc074323032312d31302d30315431333a33303a30325a5840273ec1b59817d571"
|
||||
"b5a2c5c0ab0ea213d42acb18547fd7097afcc888a22ecbb863c6461ce0e240880895b4aaa84308"
|
||||
"784571c7be7aa3a2e7e3a2ea1a145ed1966c6465766963655369676e6564a26a6e616d65537061"
|
||||
"636573d81841a06a64657669636541757468a1696465766963654d61638443a10105a0f6582009"
|
||||
"da7c964ac004ec36ec64edd0c1abf50c03433c215c3ddb144768abcdf20a60667374617475730"
|
||||
"0")
|
||||
.value();
|
||||
auto [deviceResponseItem, _, _2] = cppbor::parse(deviceResponseEncoded);
|
||||
const cppbor::Map* deviceResponse = deviceResponseItem->asMap();
|
||||
ASSERT_NE(deviceResponse, nullptr);
|
||||
const cppbor::Array* documents = findArrayValueForTstr(deviceResponse, "documents");
|
||||
ASSERT_NE(documents, nullptr);
|
||||
ASSERT_EQ(documents->size(), 1);
|
||||
const cppbor::Map* document = ((*documents)[0])->asMap();
|
||||
ASSERT_NE(document, nullptr);
|
||||
|
||||
// Get docType
|
||||
string docType = findStringValueForTstr(document, "docType");
|
||||
ASSERT_EQ(docType, "org.iso.18013.5.1.mDL");
|
||||
|
||||
// Drill down...
|
||||
const cppbor::Map* deviceSigned = findMapValueForTstr(document, "deviceSigned");
|
||||
ASSERT_NE(deviceSigned, nullptr);
|
||||
|
||||
// Dig out the encoded form of DeviceNameSpaces
|
||||
//
|
||||
const cppbor::Semantic* deviceNameSpacesBytes =
|
||||
findSemanticValueForTstr(deviceSigned, "nameSpaces");
|
||||
ASSERT_NE(deviceNameSpacesBytes, nullptr);
|
||||
ASSERT_EQ(deviceNameSpacesBytes->value(), 24);
|
||||
const cppbor::Bstr* deviceNameSpacesBstr = deviceNameSpacesBytes->child()->asBstr();
|
||||
ASSERT_NE(deviceNameSpacesBstr, nullptr);
|
||||
vector<uint8_t> deviceNameSpacesEncoded = deviceNameSpacesBstr->value();
|
||||
|
||||
// (For this version of 18013-5, DeviceNameSpaces is always supposed to be empty, check that.)
|
||||
EXPECT_EQ(deviceNameSpacesEncoded, cppbor::Map().encode());
|
||||
|
||||
const cppbor::Map* deviceAuth = findMapValueForTstr(deviceSigned, "deviceAuth");
|
||||
ASSERT_NE(deviceAuth, nullptr);
|
||||
// deviceMac is is the COSE_Mac0.. dig out the encoded form to check that
|
||||
// support::calcMac() gives exactly the same bytes.
|
||||
//
|
||||
const cppbor::Array* deviceMac = findArrayValueForTstr(deviceAuth, "deviceMac");
|
||||
ASSERT_NE(deviceMac, nullptr);
|
||||
vector<uint8_t> deviceMacEncoded = deviceMac->encode();
|
||||
|
||||
// Now we calculate what it should be..
|
||||
optional<vector<uint8_t>> calculatedMac =
|
||||
support::calcMac(sessionTranscriptEncoded, // SessionTranscript
|
||||
docType, // DocType
|
||||
deviceNameSpacesEncoded, // DeviceNamespaces
|
||||
eMacKey.value()); // EMacKey
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
|
||||
// ... and hopefully it's the same!
|
||||
ASSERT_EQ(calculatedMac.value().size(), deviceMacEncoded.size());
|
||||
EXPECT_TRUE(memcmp(calculatedMac.value().data(), deviceMacEncoded.data(),
|
||||
deviceMacEncoded.size()) == 0);
|
||||
}
|
||||
|
||||
} // namespace identity
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
|
|
@ -102,6 +102,7 @@ typedef struct km_auth_list {
|
|||
ASN1_INTEGER* boot_patchlevel;
|
||||
ASN1_NULL* early_boot_only;
|
||||
ASN1_NULL* device_unique_attestation;
|
||||
ASN1_NULL* identity_credential_key;
|
||||
} KM_AUTH_LIST;
|
||||
|
||||
ASN1_SEQUENCE(KM_AUTH_LIST) = {
|
||||
|
@ -145,6 +146,8 @@ ASN1_SEQUENCE(KM_AUTH_LIST) = {
|
|||
ASN1_EXP_OPT(KM_AUTH_LIST, early_boot_only, ASN1_NULL, TAG_EARLY_BOOT_ONLY.maskedTag()),
|
||||
ASN1_EXP_OPT(KM_AUTH_LIST, device_unique_attestation, ASN1_NULL,
|
||||
TAG_DEVICE_UNIQUE_ATTESTATION.maskedTag()),
|
||||
ASN1_EXP_OPT(KM_AUTH_LIST, identity_credential_key, ASN1_NULL,
|
||||
TAG_IDENTITY_CREDENTIAL_KEY.maskedTag()),
|
||||
} ASN1_SEQUENCE_END(KM_AUTH_LIST);
|
||||
IMPLEMENT_ASN1_FUNCTIONS(KM_AUTH_LIST);
|
||||
|
||||
|
@ -285,6 +288,7 @@ static ErrorCode extract_auth_list(const KM_AUTH_LIST* record, AuthorizationSet*
|
|||
copyAuthTag(record->unlocked_device_required, TAG_UNLOCKED_DEVICE_REQUIRED, auth_list);
|
||||
copyAuthTag(record->early_boot_only, TAG_EARLY_BOOT_ONLY, auth_list);
|
||||
copyAuthTag(record->device_unique_attestation, TAG_DEVICE_UNIQUE_ATTESTATION, auth_list);
|
||||
copyAuthTag(record->identity_credential_key, TAG_IDENTITY_CREDENTIAL_KEY, auth_list);
|
||||
|
||||
return ErrorCode::OK;
|
||||
}
|
||||
|
@ -327,7 +331,10 @@ std::tuple<ErrorCode, AttestationRecord> parse_attestation_record(const hidl_vec
|
|||
|
||||
p = attest_rec->data;
|
||||
KM_KEY_DESCRIPTION_Ptr record(d2i_KM_KEY_DESCRIPTION(nullptr, &p, attest_rec->length));
|
||||
if (!record.get()) return {ErrorCode::UNKNOWN_ERROR, {}};
|
||||
if (!record.get()) {
|
||||
LOG(ERROR) << "Unable to get key description";
|
||||
return {ErrorCode::UNKNOWN_ERROR, {}};
|
||||
}
|
||||
|
||||
AttestationRecord result;
|
||||
|
||||
|
@ -352,10 +359,12 @@ std::tuple<ErrorCode, AttestationRecord> parse_attestation_record(const hidl_vec
|
|||
if (error != ErrorCode::OK) return {error, {}};
|
||||
|
||||
KM_ROOT_OF_TRUST* root_of_trust = nullptr;
|
||||
SecurityLevel root_of_trust_security_level = SecurityLevel::TRUSTED_ENVIRONMENT;
|
||||
if (record->tee_enforced && record->tee_enforced->root_of_trust) {
|
||||
root_of_trust = record->tee_enforced->root_of_trust;
|
||||
} else if (record->software_enforced && record->software_enforced->root_of_trust) {
|
||||
root_of_trust = record->software_enforced->root_of_trust;
|
||||
root_of_trust_security_level = SecurityLevel::SOFTWARE;
|
||||
} else {
|
||||
LOG(ERROR) << AT << " Failed root of trust parsing";
|
||||
return {ErrorCode::INVALID_ARGUMENT, {}};
|
||||
|
@ -373,6 +382,7 @@ std::tuple<ErrorCode, AttestationRecord> parse_attestation_record(const hidl_vec
|
|||
rot.verified_boot_state = static_cast<keymaster_verified_boot_t>(
|
||||
ASN1_ENUMERATED_get(root_of_trust->verified_boot_state));
|
||||
rot.device_locked = root_of_trust->device_locked;
|
||||
rot.security_level = root_of_trust_security_level;
|
||||
|
||||
auto& vb_hash = root_of_trust->verified_boot_hash;
|
||||
if (!vb_hash) {
|
||||
|
|
Loading…
Reference in a new issue