mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:50:18 +00:00
Merge "identity: Fix attestation and documentation problems." am: d47c62b62a
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1475343 Change-Id: I94a44ba671fd41c1cdfce92cbf7541d2faf87ea1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -272,6 +272,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
||||
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
|
||||
const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
|
||||
std::unique_ptr<cppbor::Item> sessionTranscriptItem;
|
||||
if (sessionTranscript.size() > 0) {
|
||||
auto [item, _, message] = cppbor::parse(sessionTranscript);
|
||||
if (item == nullptr) {
|
||||
@@ -279,7 +280,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) {
|
||||
@@ -319,7 +320,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 =
|
||||
@@ -776,13 +777,6 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_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);
|
||||
@@ -792,31 +786,15 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_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"));
|
||||
@@ -830,9 +808,9 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
|
||||
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
|
||||
vector<uint8_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);
|
||||
}
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VtsAttestationTests);
|
||||
|
||||
@@ -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;
|
||||
error = generate_attestation_from_EVP(key, swEnforced, hwEnforced, auth_set, context,
|
||||
identity_version, *attestation_chain,
|
||||
*attestation_signing_key, &cert_chain_out);
|
||||
// Pretend to be implemented in a trusted environment just so we can pass
|
||||
// the VTS tests. Of course, this is a pretend-only game since hopefully no
|
||||
// relying party is ever going to trust our batch key and those keys above
|
||||
// it.
|
||||
//
|
||||
::keymaster::PureSoftKeymasterContext context(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
|
||||
|
||||
error = generate_attestation_from_EVP_with_subject_name(
|
||||
key, swEnforced, hwEnforced, auth_set, context, ::keymaster::kCurrentKeymasterVersion,
|
||||
*attestation_chain, *attestation_signing_key, "Android Identity Credential Key",
|
||||
&cert_chain_out);
|
||||
|
||||
if (KM_ERROR_OK != error || !cert_chain_out) {
|
||||
LOG(ERROR) << "Error generate attestation from EVP key" << error;
|
||||
@@ -957,7 +1034,8 @@ optional<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 {};
|
||||
@@ -1646,6 +1721,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)) {
|
||||
@@ -2218,6 +2319,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) {
|
||||
|
||||
Reference in New Issue
Block a user