Merge "Update Identity Credential VTS tests." am: 17ec80b638

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1322407

Change-Id: I6ff5d40629c6754897f8eb3f11010966dfb93190
This commit is contained in:
Treehugger Robot
2020-06-05 13:50:24 +00:00
committed by Automerger Merge Worker
13 changed files with 1286 additions and 120 deletions

View File

@@ -160,17 +160,10 @@ interface IIdentityCredential {
* ItemsRequestBytes
* ]
*
* SessionTranscript = [
* DeviceEngagementBytes,
* EReaderKeyBytes
* ]
* SessionTranscript = any
*
* DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
* ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
*
* EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader
*
* The public key corresponding to the key used to made signature, can be found in the
* 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described
* in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element
@@ -184,8 +177,12 @@ interface IIdentityCredential {
*
* If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public
* part of the key-pair previously generated by createEphemeralKeyPair() must appear
* somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in
* uncompressed form. If this is not satisfied, the call fails with
* somewhere in the bytes of the CBOR. Each of these coordinates must appear encoded
* with the most significant bits first and use the exact amount of bits indicated by
* the key size of the ephemeral keys. For example, if the ephemeral key is using the
* P-256 curve then the 32 bytes for the X coordinate encoded with the most significant
* bits first must appear somewhere in the CBOR and ditto for the 32 bytes for the Y
* coordinate. If this is not satisfied, the call fails with
* STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND.
*
* @param accessControlProfiles
@@ -298,13 +295,8 @@ interface IIdentityCredential {
*
* DocType = tstr
*
* SessionTranscript = [
* DeviceEngagementBytes,
* EReaderKeyBytes
* ]
* SessionTranscript = any
*
* DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
* DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
*
* where
@@ -356,8 +348,9 @@ interface IIdentityCredential {
*
* - subjectPublicKeyInfo: must contain attested public key.
*
* @param out signingKeyBlob contains an encrypted copy of the newly-generated private
* signing key.
* @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType)
* where signingKey is an EC private key in uncompressed form. That is, the returned
* blob is an encrypted copy of the newly-generated private signing key.
*
* @return an X.509 certificate for the new signing key, signed by the credential key.
*/

View File

@@ -29,9 +29,27 @@ interface IWritableIdentityCredential {
* Gets the certificate chain for credentialKey which can be used to prove the hardware
* characteristics to an issuing authority. Must not be called more than once.
*
* The following non-optional fields for the X.509 certificate shall be set as follows:
*
* - version: INTEGER 2 (means v3 certificate).
*
* - serialNumber: INTEGER 1 (fixed value: same on all certs).
*
* - signature: must be set to ECDSA.
*
* - subject: CN shall be set to "Android Identity Credential Key".
*
* - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the
* values returned in HardwareInformation.
*
* - validity: should be from current time and expire at the same time as the
* attestation batch certificate used.
*
* - subjectPublicKeyInfo: must contain attested public key.
*
* The certificate chain must be generated using Keymaster Attestation
* (see https://source.android.com/security/keystore/attestation) with the
* following additional requirements:
* following additional requirements on the data in the attestation extension:
*
* - The attestationVersion field in the attestation extension must be at least 3.
*
@@ -109,7 +127,8 @@ interface IWritableIdentityCredential {
* in Tag::ATTESTATION_APPLICATION_ID. This schema is described in
* https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid
*
* @param attestationChallenge a challenge set by the issuer to ensure freshness.
* @param attestationChallenge a challenge set by the issuer to ensure freshness. If
* this is empty, the call fails with STATUS_INVALID_DATA.
*
* @return the X.509 certificate chain for the credentialKey
*/
@@ -250,6 +269,7 @@ interface IWritableIdentityCredential {
* CredentialKeys = [
* bstr, ; storageKey, a 128-bit AES key
* bstr ; credentialPrivKey, the private key for credentialKey
* ; in uncompressed form
* ]
*
* @param out proofOfProvisioningSignature proves to the IA that the credential was imported

View File

@@ -164,6 +164,7 @@ ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge
}
*outChallenge = challenge;
authChallenge_ = challenge;
return ndk::ScopedAStatus::ok();
}
@@ -223,7 +224,8 @@ bool checkUserAuthentication(const SecureAccessControlProfile& profile,
}
if (authToken.challenge != int64_t(authChallenge)) {
LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
<< "doesn't match the challenge we created (" << authChallenge << ")";
return false;
}
return true;
@@ -337,28 +339,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
//
// We do this by just searching for the X and Y coordinates.
if (sessionTranscript.size() > 0) {
const cppbor::Array* array = sessionTranscriptItem_->asArray();
if (array == nullptr || array->size() != 2) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
"SessionTranscript is not an array with two items"));
}
const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
"First item in SessionTranscript array is not a "
"semantic with value 24"));
}
const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
if (encodedDE == nullptr) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
"Child of semantic in first item in SessionTranscript "
"array is not a bstr"));
}
const vector<uint8_t>& bytesDE = encodedDE->value();
auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
if (!getXYSuccess) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -366,8 +346,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
"Error extracting X and Y from ePub"));
}
if (sessionTranscript.size() > 0 &&
!(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
!(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
ePubX.size()) != nullptr &&
memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
ePubY.size()) != nullptr)) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
"Did not find ephemeral public key's X and Y coordinates in "
@@ -474,9 +456,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
}
// Validate all the access control profiles in the requestData.
bool haveAuthToken = (authToken.mac.size() > 0);
bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
for (const auto& profile : accessControlProfiles) {
if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
LOG(ERROR) << "Error checking MAC for profile";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
"Error checking MAC for profile"));

View File

@@ -65,6 +65,10 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
IIdentityCredentialStore::STATUS_FAILED,
"Error attestation certificate previously generated"));
}
if (attestationChallenge.empty()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
}
vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
@@ -165,6 +169,13 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
"userAuthenticationRequired is false but timeout is non-zero"));
}
// If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero.
if (userAuthenticationRequired && secureUserId == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
"userAuthenticationRequired is true but secureUserId is zero"));
}
profile.id = id;
profile.readerCertificate = readerCertificate;
profile.userAuthenticationRequired = userAuthenticationRequired;

View File

@@ -10,6 +10,8 @@ cc_test {
"VtsIdentityTestUtils.cpp",
"VtsAttestationTests.cpp",
"VtsAttestationParserSupport.cpp",
"UserAuthTests.cpp",
"ReaderAuthTests.cpp",
],
shared_libs: [
"android.hardware.keymaster@4.0",
@@ -18,6 +20,7 @@ cc_test {
"libkeymaster_portable",
"libsoft_attestation_cert",
"libpuresoftkeymasterdevice",
"android.hardware.keymaster-ndk_platform",
],
static_libs: [
"libcppbor",

View File

@@ -0,0 +1,596 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "ReaderAuthTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "VtsIdentityTestUtils.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class ReaderAuthTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
}
void provisionData();
void retrieveData(const vector<uint8_t>& readerPrivateKey,
const vector<vector<uint8_t>>& readerCertChain, bool expectSuccess,
bool leaveOutAccessibleToAllFromRequestMessage);
// Set by provisionData
vector<uint8_t> readerPublicKey_;
vector<uint8_t> readerPrivateKey_;
vector<uint8_t> intermediateAPublicKey_;
vector<uint8_t> intermediateAPrivateKey_;
vector<uint8_t> intermediateBPublicKey_;
vector<uint8_t> intermediateBPrivateKey_;
vector<uint8_t> intermediateCPublicKey_;
vector<uint8_t> intermediateCPrivateKey_;
vector<uint8_t> cert_A_SelfSigned_;
vector<uint8_t> cert_B_SelfSigned_;
vector<uint8_t> cert_B_SignedBy_C_;
vector<uint8_t> cert_C_SelfSigned_;
vector<uint8_t> cert_reader_SelfSigned_;
vector<uint8_t> cert_reader_SignedBy_A_;
vector<uint8_t> cert_reader_SignedBy_B_;
SecureAccessControlProfile sacp0_;
SecureAccessControlProfile sacp1_;
SecureAccessControlProfile sacp2_;
SecureAccessControlProfile sacp3_;
vector<uint8_t> encContentAccessibleByA_;
vector<uint8_t> encContentAccessibleByAorB_;
vector<uint8_t> encContentAccessibleByB_;
vector<uint8_t> encContentAccessibleByC_;
vector<uint8_t> encContentAccessibleByAll_;
vector<uint8_t> encContentAccessibleByNone_;
vector<uint8_t> credentialData_;
// Set by retrieveData()
bool canGetAccessibleByA_;
bool canGetAccessibleByAorB_;
bool canGetAccessibleByB_;
bool canGetAccessibleByC_;
bool canGetAccessibleByAll_;
bool canGetAccessibleByNone_;
sp<IIdentityCredentialStore> credentialStore_;
};
pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey() {
optional<vector<uint8_t>> keyPKCS8 = support::createEcKeyPair();
optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPKCS8.value());
optional<vector<uint8_t>> privateKey = support::ecKeyPairGetPrivateKey(keyPKCS8.value());
return make_pair(publicKey.value(), privateKey.value());
}
vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
const vector<uint8_t>& signingKey) {
time_t validityNotBefore = 0;
time_t validityNotAfter = 0xffffffff;
optional<vector<uint8_t>> cert =
support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer",
"Subject", validityNotBefore, validityNotAfter);
return cert.value();
}
void ReaderAuthTests::provisionData() {
// Keys and certificates for intermediates.
tie(intermediateAPublicKey_, intermediateAPrivateKey_) = generateReaderKey();
tie(intermediateBPublicKey_, intermediateBPrivateKey_) = generateReaderKey();
tie(intermediateCPublicKey_, intermediateCPrivateKey_) = generateReaderKey();
cert_A_SelfSigned_ = generateReaderCert(intermediateAPublicKey_, intermediateAPrivateKey_);
cert_B_SelfSigned_ = generateReaderCert(intermediateBPublicKey_, intermediateBPrivateKey_);
cert_B_SignedBy_C_ = generateReaderCert(intermediateBPublicKey_, intermediateCPrivateKey_);
cert_C_SelfSigned_ = generateReaderCert(intermediateCPublicKey_, intermediateCPrivateKey_);
// Key and self-signed certificate reader
tie(readerPublicKey_, readerPrivateKey_) = generateReaderKey();
cert_reader_SelfSigned_ = generateReaderCert(readerPublicKey_, readerPrivateKey_);
// Certificate for reader signed by intermediates
cert_reader_SignedBy_A_ = generateReaderCert(readerPublicKey_, intermediateAPrivateKey_);
cert_reader_SignedBy_B_ = generateReaderCert(readerPublicKey_, intermediateBPrivateKey_);
string docType = "org.iso.18013-5.2019.mdl";
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
size_t proofOfProvisioningSize =
465 + cert_A_SelfSigned_.size() + cert_B_SelfSigned_.size() + cert_C_SelfSigned_.size();
ASSERT_TRUE(wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize).isOk());
// Not in v1 HAL, may fail
wc->startPersonalization(4 /* numAccessControlProfiles */,
{6} /* numDataElementsPerNamespace */);
// AIDL expects certificates wrapped in the Certificate type...
Certificate cert_A;
Certificate cert_B;
Certificate cert_C;
cert_A.encodedCertificate = cert_A_SelfSigned_;
cert_B.encodedCertificate = cert_B_SelfSigned_;
cert_C.encodedCertificate = cert_C_SelfSigned_;
// Access control profile 0: accessible by A
ASSERT_TRUE(wc->addAccessControlProfile(0, cert_A, false, 0, 0, &sacp0_).isOk());
// Access control profile 1: accessible by B
ASSERT_TRUE(wc->addAccessControlProfile(1, cert_B, false, 0, 0, &sacp1_).isOk());
// Access control profile 2: accessible by C
ASSERT_TRUE(wc->addAccessControlProfile(2, cert_C, false, 0, 0, &sacp2_).isOk());
// Access control profile 3: open access
ASSERT_TRUE(wc->addAccessControlProfile(3, {}, false, 0, 0, &sacp3_).isOk());
// Data Element: "Accessible by A"
ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Accessible by A", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByA_).isOk());
// Data Element: "Accessible by A or B"
ASSERT_TRUE(wc->beginAddEntry({0, 1}, "ns", "Accessible by A or B", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAorB_).isOk());
// Data Element: "Accessible by B"
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Accessible by B", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByB_).isOk());
// Data Element: "Accessible by C"
ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by C", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByC_).isOk());
// Data Element: "Accessible by All"
ASSERT_TRUE(wc->beginAddEntry({3}, "ns", "Accessible by All", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk());
// Data Element: "Accessible by None"
ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk());
vector<uint8_t> proofOfProvisioningSignature;
ASSERT_TRUE(wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature).isOk());
}
RequestDataItem buildRequestDataItem(const string& name, size_t size,
vector<int32_t> accessControlProfileIds) {
RequestDataItem item;
item.name = name;
item.size = size;
item.accessControlProfileIds = accessControlProfileIds;
return item;
}
void ReaderAuthTests::retrieveData(const vector<uint8_t>& readerPrivateKey,
const vector<vector<uint8_t>>& readerCertChain,
bool expectSuccess,
bool leaveOutAccessibleToAllFromRequestMessage) {
canGetAccessibleByA_ = false;
canGetAccessibleByAorB_ = false;
canGetAccessibleByB_ = false;
canGetAccessibleByC_ = false;
canGetAccessibleByAll_ = false;
canGetAccessibleByNone_ = false;
sp<IIdentityCredential> c;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &c)
.isOk());
optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
optional<vector<uint8_t>> readerEPublicKey =
support::ecKeyPairGetPublicKey(readerEKeyPair.value());
ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
vector<uint8_t> eKeyPair;
ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
// Calculate requestData field and sign it with the reader key.
auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value());
ASSERT_TRUE(getXYSuccess);
cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
cppbor::Array sessionTranscript = cppbor::Array()
.add(cppbor::Semantic(24, deviceEngagementBytes))
.add(cppbor::Semantic(24, eReaderPubBytes));
vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
vector<uint8_t> itemsRequestBytes;
if (leaveOutAccessibleToAllFromRequestMessage) {
itemsRequestBytes =
cppbor::Map("nameSpaces",
cppbor::Map().add("ns", cppbor::Map()
.add("Accessible by A", false)
.add("Accessible by A or B", false)
.add("Accessible by B", false)
.add("Accessible by C", false)
.add("Accessible by None", false)))
.encode();
} else {
itemsRequestBytes =
cppbor::Map("nameSpaces",
cppbor::Map().add("ns", cppbor::Map()
.add("Accessible by A", false)
.add("Accessible by A or B", false)
.add("Accessible by B", false)
.add("Accessible by C", false)
.add("Accessible by All", false)
.add("Accessible by None", false)))
.encode();
}
vector<uint8_t> dataToSign = cppbor::Array()
.add("ReaderAuthentication")
.add(sessionTranscript.clone())
.add(cppbor::Semantic(24, itemsRequestBytes))
.encode();
optional<vector<uint8_t>> readerSignature =
support::coseSignEcDsa(readerPrivateKey, // private key for reader
{}, // content
dataToSign, // detached content
support::certificateChainJoin(readerCertChain));
ASSERT_TRUE(readerSignature);
// Generate the key that will be used to sign AuthenticatedData.
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
RequestNamespace rns;
rns.namespaceName = "ns";
rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0}));
rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1}));
rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1}));
rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2}));
rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3}));
rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
// OK to fail, not available in v1 HAL
c->setRequestedNamespaces({rns}).isOk();
// It doesn't matter since no user auth is needed in this particular test,
// but for good measure, clear out the tokens we pass to the HAL.
HardwareAuthToken authToken;
VerificationToken verificationToken;
authToken.challenge = 0;
authToken.userId = 0;
authToken.authenticatorId = 0;
authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
authToken.timestamp.milliSeconds = 0;
authToken.mac.clear();
verificationToken.challenge = 0;
verificationToken.timestamp.milliSeconds = 0;
verificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE;
verificationToken.mac.clear();
// OK to fail, not available in v1 HAL
c->setVerificationToken(verificationToken);
Status status = c->startRetrieval(
{sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob,
sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */});
if (expectSuccess) {
ASSERT_TRUE(status.isOk());
} else {
ASSERT_FALSE(status.isOk());
return;
}
vector<uint8_t> decrypted;
status = c->startRetrieveEntryValue("ns", "Accessible by A", 1, {0});
if (status.isOk()) {
canGetAccessibleByA_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByA_, &decrypted).isOk());
}
status = c->startRetrieveEntryValue("ns", "Accessible by A or B", 1, {0, 1});
if (status.isOk()) {
canGetAccessibleByAorB_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAorB_, &decrypted).isOk());
}
status = c->startRetrieveEntryValue("ns", "Accessible by B", 1, {1});
if (status.isOk()) {
canGetAccessibleByB_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByB_, &decrypted).isOk());
}
status = c->startRetrieveEntryValue("ns", "Accessible by C", 1, {2});
if (status.isOk()) {
canGetAccessibleByC_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByC_, &decrypted).isOk());
}
status = c->startRetrieveEntryValue("ns", "Accessible by All", 1, {3});
if (status.isOk()) {
canGetAccessibleByAll_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk());
}
status = c->startRetrieveEntryValue("ns", "Accessible by None", 1, {});
if (status.isOk()) {
canGetAccessibleByNone_ = true;
ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk());
}
vector<uint8_t> mac;
vector<uint8_t> deviceNameSpaces;
ASSERT_TRUE(c->finishRetrieval(&mac, &deviceNameSpaces).isOk());
}
TEST_P(ReaderAuthTests, presentingChain_Reader) {
provisionData();
retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */,
false /* leaveOutAccessibleToAllFromRequestMessage */);
EXPECT_FALSE(canGetAccessibleByA_);
EXPECT_FALSE(canGetAccessibleByAorB_);
EXPECT_FALSE(canGetAccessibleByB_);
EXPECT_FALSE(canGetAccessibleByC_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(ReaderAuthTests, presentingChain_Reader_A) {
provisionData();
retrieveData(readerPrivateKey_, {cert_reader_SignedBy_A_, cert_A_SelfSigned_},
true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
EXPECT_TRUE(canGetAccessibleByA_);
EXPECT_TRUE(canGetAccessibleByAorB_);
EXPECT_FALSE(canGetAccessibleByB_);
EXPECT_FALSE(canGetAccessibleByC_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(ReaderAuthTests, presentingChain_Reader_B) {
provisionData();
retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SelfSigned_},
true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
EXPECT_FALSE(canGetAccessibleByA_);
EXPECT_TRUE(canGetAccessibleByAorB_);
EXPECT_TRUE(canGetAccessibleByB_);
EXPECT_FALSE(canGetAccessibleByC_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
// This test proves that for the purpose of determining inclusion of an ACP certificate
// in a presented reader chain, certificate equality is done by comparing public keys,
// not bitwise comparison of the certificates.
//
// Specifically for this test, the ACP is configured with cert_B_SelfSigned_ and the
// reader is presenting cert_B_SignedBy_C_. Both certificates have the same public
// key - intermediateBPublicKey_ - but they are signed by different keys.
//
TEST_P(ReaderAuthTests, presentingChain_Reader_B_C) {
provisionData();
retrieveData(readerPrivateKey_,
{cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_},
true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
EXPECT_FALSE(canGetAccessibleByA_);
EXPECT_TRUE(canGetAccessibleByAorB_);
EXPECT_TRUE(canGetAccessibleByB_);
EXPECT_TRUE(canGetAccessibleByC_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
// This test presents a reader chain where the chain is invalid because
// the 2nd certificate in the chain isn't signed by the 3rd one.
//
TEST_P(ReaderAuthTests, presentingInvalidChain) {
provisionData();
retrieveData(readerPrivateKey_,
{cert_reader_SignedBy_B_, cert_B_SelfSigned_, cert_C_SelfSigned_},
false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
}
// This tests presents a valid reader chain but where requestMessage isn't
// signed by the private key corresponding to the public key in the top-level
// certificate.
//
TEST_P(ReaderAuthTests, presentingMessageSignedNotByTopLevel) {
provisionData();
retrieveData(intermediateBPrivateKey_,
{cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_},
false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
}
// This test leaves out "Accessible by All" data element from the signed request
// message (the CBOR from the reader) while still including this data element at
// the API level. The call on the API level for said element will fail with
// STATUS_NOT_IN_REQUEST_MESSAGE but this doesn't prevent the other elements
// from being returned (if authorized, of course).
//
// This test verifies that.
//
TEST_P(ReaderAuthTests, limitedMessage) {
provisionData();
retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */,
true /* leaveOutAccessibleToAllFromRequestMessage */);
EXPECT_FALSE(canGetAccessibleByA_);
EXPECT_FALSE(canGetAccessibleByAorB_);
EXPECT_FALSE(canGetAccessibleByB_);
EXPECT_FALSE(canGetAccessibleByC_);
EXPECT_FALSE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(ReaderAuthTests, ephemeralKeyNotInSessionTranscript) {
provisionData();
sp<IIdentityCredential> c;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &c)
.isOk());
optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
optional<vector<uint8_t>> readerEPublicKey =
support::ecKeyPairGetPublicKey(readerEKeyPair.value());
ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
vector<uint8_t> eKeyPair;
ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
// Calculate requestData field and sign it with the reader key.
auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value());
ASSERT_TRUE(getXYSuccess);
// Instead of include the X and Y coordinates (|ephX| and |ephY|), add NUL bytes instead.
vector<uint8_t> nulls(32);
cppbor::Map deviceEngagement = cppbor::Map().add("ephX", nulls).add("ephY", nulls);
vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
cppbor::Array sessionTranscript = cppbor::Array()
.add(cppbor::Semantic(24, deviceEngagementBytes))
.add(cppbor::Semantic(24, eReaderPubBytes));
vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
vector<uint8_t> itemsRequestBytes;
itemsRequestBytes =
cppbor::Map("nameSpaces",
cppbor::Map().add("ns", cppbor::Map()
.add("Accessible by A", false)
.add("Accessible by A or B", false)
.add("Accessible by B", false)
.add("Accessible by C", false)
.add("Accessible by None", false)))
.encode();
vector<uint8_t> dataToSign = cppbor::Array()
.add("ReaderAuthentication")
.add(sessionTranscript.clone())
.add(cppbor::Semantic(24, itemsRequestBytes))
.encode();
vector<vector<uint8_t>> readerCertChain = {cert_reader_SelfSigned_};
optional<vector<uint8_t>> readerSignature =
support::coseSignEcDsa(readerPrivateKey_, // private key for reader
{}, // content
dataToSign, // detached content
support::certificateChainJoin(readerCertChain));
ASSERT_TRUE(readerSignature);
// Generate the key that will be used to sign AuthenticatedData.
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
RequestNamespace rns;
rns.namespaceName = "ns";
rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0}));
rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1}));
rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1}));
rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2}));
rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3}));
rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
// OK to fail, not available in v1 HAL
c->setRequestedNamespaces({rns}).isOk();
// It doesn't matter since no user auth is needed in this particular test,
// but for good measure, clear out the tokens we pass to the HAL.
HardwareAuthToken authToken;
VerificationToken verificationToken;
authToken.challenge = 0;
authToken.userId = 0;
authToken.authenticatorId = 0;
authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
authToken.timestamp.milliSeconds = 0;
authToken.mac.clear();
verificationToken.challenge = 0;
verificationToken.timestamp.milliSeconds = 0;
verificationToken.securityLevel =
::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT;
verificationToken.mac.clear();
// OK to fail, not available in v1 HAL
c->setVerificationToken(verificationToken);
// Finally check that STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND is returned.
// This proves that the TA checked for X and Y coordinatets and didn't find
// them.
Status status = c->startRetrieval(
{sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob,
sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */});
ASSERT_FALSE(status.isOk());
ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
ASSERT_EQ(IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
status.serviceSpecificErrorCode());
}
INSTANTIATE_TEST_SUITE_P(
Identity, ReaderAuthTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -0,0 +1,473 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "UserAuthTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "VtsIdentityTestUtils.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class UserAuthTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
}
void provisionData();
void setupRetrieveData();
pair<HardwareAuthToken, VerificationToken> mintTokens(uint64_t challengeForAuthToken,
int64_t ageOfAuthTokenMilliSeconds);
void retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken,
bool expectSuccess, bool useSessionTranscript);
// Set by provisionData
SecureAccessControlProfile sacp0_;
SecureAccessControlProfile sacp1_;
SecureAccessControlProfile sacp2_;
vector<uint8_t> encContentUserAuthPerSession_;
vector<uint8_t> encContentUserAuthTimeout_;
vector<uint8_t> encContentAccessibleByAll_;
vector<uint8_t> encContentAccessibleByNone_;
vector<uint8_t> credentialData_;
// Set by setupRetrieveData().
int64_t authChallenge_;
cppbor::Map sessionTranscript_;
sp<IIdentityCredential> credential_;
// Set by retrieveData()
bool canGetUserAuthPerSession_;
bool canGetUserAuthTimeout_;
bool canGetAccessibleByAll_;
bool canGetAccessibleByNone_;
sp<IIdentityCredentialStore> credentialStore_;
};
void UserAuthTests::provisionData() {
string docType = "org.iso.18013-5.2019.mdl";
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
size_t proofOfProvisioningSize = 381;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(3 /* numAccessControlProfiles */,
{4} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: user auth every session (timeout = 0)
ASSERT_TRUE(wc->addAccessControlProfile(0, {}, true, 0, 65 /* secureUserId */, &sacp0_).isOk());
// Access control profile 1: user auth, 60 seconds timeout
ASSERT_TRUE(
wc->addAccessControlProfile(1, {}, true, 60000, 65 /* secureUserId */, &sacp1_).isOk());
// Access control profile 2: open access
ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp2_).isOk());
// Data Element: "UserAuth Per Session"
ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "UserAuth Per Session", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthPerSession_).isOk());
// Data Element: "UserAuth Timeout"
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "UserAuth Timeout", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthTimeout_).isOk());
// Data Element: "Accessible by All"
ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by All", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk());
// Data Element: "Accessible by None"
ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk());
ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h
pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey();
vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
const vector<uint8_t>& signingKey);
RequestDataItem buildRequestDataItem(const string& name, size_t size,
vector<int32_t> accessControlProfileIds);
cppbor::Map calcSessionTranscript(const vector<uint8_t>& ePublicKey) {
auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey);
cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
// Let SessionTranscript be a map here (it's an array in EndToEndTest) just
// to check that the implementation can deal with either.
cppbor::Map sessionTranscript;
sessionTranscript.add(42, cppbor::Semantic(24, deviceEngagementBytes));
sessionTranscript.add(43, cppbor::Semantic(24, eReaderPubBytes));
return sessionTranscript;
}
void UserAuthTests::setupRetrieveData() {
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &credential_)
.isOk());
optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
optional<vector<uint8_t>> readerEPublicKey =
support::ecKeyPairGetPublicKey(readerEKeyPair.value());
ASSERT_TRUE(credential_->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
vector<uint8_t> eKeyPair;
ASSERT_TRUE(credential_->createEphemeralKeyPair(&eKeyPair).isOk());
optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
sessionTranscript_ = calcSessionTranscript(ePublicKey.value());
Status status = credential_->createAuthChallenge(&authChallenge_);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
void UserAuthTests::retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken,
bool expectSuccess, bool useSessionTranscript) {
canGetUserAuthPerSession_ = false;
canGetUserAuthTimeout_ = false;
canGetAccessibleByAll_ = false;
canGetAccessibleByNone_ = false;
vector<uint8_t> itemsRequestBytes;
vector<uint8_t> sessionTranscriptBytes;
if (useSessionTranscript) {
sessionTranscriptBytes = sessionTranscript_.encode();
itemsRequestBytes =
cppbor::Map("nameSpaces",
cppbor::Map().add("ns", cppbor::Map()
.add("UserAuth Per Session", false)
.add("UserAuth Timeout", false)
.add("Accessible by All", false)
.add("Accessible by None", false)))
.encode();
vector<uint8_t> dataToSign = cppbor::Array()
.add("ReaderAuthentication")
.add(sessionTranscript_.clone())
.add(cppbor::Semantic(24, itemsRequestBytes))
.encode();
}
// Generate the key that will be used to sign AuthenticatedData.
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(
credential_->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
RequestNamespace rns;
rns.namespaceName = "ns";
rns.items.push_back(buildRequestDataItem("UserAuth Per Session", 1, {0}));
rns.items.push_back(buildRequestDataItem("UserAuth Timeout", 1, {1}));
rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {2}));
rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
// OK to fail, not available in v1 HAL
credential_->setRequestedNamespaces({rns}).isOk();
// OK to fail, not available in v1 HAL
credential_->setVerificationToken(verificationToken);
Status status = credential_->startRetrieval({sacp0_, sacp1_, sacp2_}, authToken,
itemsRequestBytes, signingKeyBlob,
sessionTranscriptBytes, {} /* readerSignature */,
{4 /* numDataElementsPerNamespace */});
if (expectSuccess) {
ASSERT_TRUE(status.isOk());
} else {
ASSERT_FALSE(status.isOk());
return;
}
vector<uint8_t> decrypted;
status = credential_->startRetrieveEntryValue("ns", "UserAuth Per Session", 1, {0});
if (status.isOk()) {
canGetUserAuthPerSession_ = true;
ASSERT_TRUE(
credential_->retrieveEntryValue(encContentUserAuthPerSession_, &decrypted).isOk());
}
status = credential_->startRetrieveEntryValue("ns", "UserAuth Timeout", 1, {1});
if (status.isOk()) {
canGetUserAuthTimeout_ = true;
ASSERT_TRUE(credential_->retrieveEntryValue(encContentUserAuthTimeout_, &decrypted).isOk());
}
status = credential_->startRetrieveEntryValue("ns", "Accessible by All", 1, {2});
if (status.isOk()) {
canGetAccessibleByAll_ = true;
ASSERT_TRUE(credential_->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk());
}
status = credential_->startRetrieveEntryValue("ns", "Accessible by None", 1, {});
if (status.isOk()) {
canGetAccessibleByNone_ = true;
ASSERT_TRUE(
credential_->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk());
}
vector<uint8_t> mac;
vector<uint8_t> deviceNameSpaces;
ASSERT_TRUE(credential_->finishRetrieval(&mac, &deviceNameSpaces).isOk());
}
pair<HardwareAuthToken, VerificationToken> UserAuthTests::mintTokens(
uint64_t challengeForAuthToken, int64_t ageOfAuthTokenMilliSeconds) {
HardwareAuthToken authToken;
VerificationToken verificationToken;
uint64_t epochMilliseconds = 1000ULL * 1000ULL * 1000ULL * 1000ULL;
authToken.challenge = challengeForAuthToken;
authToken.userId = 65;
authToken.authenticatorId = 0;
authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
authToken.timestamp.milliSeconds = epochMilliseconds - ageOfAuthTokenMilliSeconds;
authToken.mac.clear();
verificationToken.challenge = authChallenge_;
verificationToken.timestamp.milliSeconds = epochMilliseconds;
verificationToken.securityLevel =
::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT;
verificationToken.mac.clear();
return make_pair(authToken, verificationToken);
}
TEST_P(UserAuthTests, GoodChallenge) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(authChallenge_, // challengeForAuthToken
0); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_TRUE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, OtherChallenge) {
provisionData();
setupRetrieveData();
uint64_t otherChallenge = authChallenge_ ^ 0x12345678;
auto [authToken, verificationToken] = mintTokens(otherChallenge, // challengeForAuthToken
0); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, NoChallenge) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
0); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, AuthTokenAgeZero) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
0); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, AuthTokenFromTheFuture) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
-1 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_FALSE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, AuthTokenInsideTimeout) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
30 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
TEST_P(UserAuthTests, AuthTokenOutsideTimeout) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
61 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_FALSE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
// The API works even when there's no SessionTranscript / itemsRequest.
// Verify that.
TEST_P(UserAuthTests, NoSessionTranscript) {
provisionData();
setupRetrieveData();
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
1 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
false /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
// This test verifies that it's possible to do multiple requests as long
// as the sessionTranscript doesn't change.
//
TEST_P(UserAuthTests, MultipleRequestsSameSessionTranscript) {
provisionData();
setupRetrieveData();
// First we try with a stale authToken
//
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
61 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_FALSE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
// Then we get a new authToken and try again.
tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken
5 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_TRUE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
}
// Like MultipleRequestsSameSessionTranscript but we change the sessionTranscript
// between the two calls. This test verifies that change is detected and the
// second request fails.
//
TEST_P(UserAuthTests, MultipleRequestsSessionTranscriptChanges) {
provisionData();
setupRetrieveData();
// First we try with a stale authToken
//
auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
61 * 1000); // ageOfAuthTokenMilliSeconds
retrieveData(authToken, verificationToken, true /* expectSuccess */,
true /* useSessionTranscript */);
EXPECT_FALSE(canGetUserAuthPerSession_);
EXPECT_FALSE(canGetUserAuthTimeout_);
EXPECT_TRUE(canGetAccessibleByAll_);
EXPECT_FALSE(canGetAccessibleByNone_);
// Then we get a new authToken and try again.
tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken
5 * 1000); // ageOfAuthTokenMilliSeconds
// Change sessionTranscript...
optional<vector<uint8_t>> eKeyPairNew = support::createEcKeyPair();
optional<vector<uint8_t>> ePublicKeyNew = support::ecKeyPairGetPublicKey(eKeyPairNew.value());
sessionTranscript_ = calcSessionTranscript(ePublicKeyNew.value());
// ... and expect failure.
retrieveData(authToken, verificationToken, false /* expectSuccess */,
true /* useSessionTranscript */);
}
INSTANTIATE_TEST_SUITE_P(
Identity, UserAuthTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -61,51 +61,6 @@ class VtsAttestationTests : public testing::TestWithParam<std::string> {
sp<IIdentityCredentialStore> credentialStore_;
};
TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeEmptyId) {
Status result;
HardwareInformation hwInfo;
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
sp<IWritableIdentityCredential> writableCredential;
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
vector<uint8_t> attestationChallenge;
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, verifyAttestationWithEmptyChallengeNonemptyId) {
Status result;
HardwareInformation hwInfo;
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
sp<IWritableIdentityCredential> writableCredential;
ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
vector<uint8_t> attestationChallenge;
vector<Certificate> attestationCertificate;
string applicationId = "Attestation Verification";
vector<uint8_t> attestationApplicationId = {applicationId.begin(), applicationId.end()};
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, verifyAttestationWithNonemptyChallengeEmptyId) {
Status result;

View File

@@ -27,15 +27,18 @@
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <tuple>
#include "VtsIdentityTestUtils.h"
namespace android::hardware::identity {
using std::endl;
using std::make_tuple;
using std::map;
using std::optional;
using std::string;
using std::tuple;
using std::vector;
using ::android::sp;
@@ -66,6 +69,61 @@ TEST_P(IdentityAidl, hardwareInformation) {
ASSERT_GE(info.dataChunkSize, 256);
}
tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
const vector<uint8_t>& credentialData) {
string docType;
vector<uint8_t> storageKey;
vector<uint8_t> credentialPrivKey;
auto [item, _, message] = cppbor::parse(credentialData);
if (item == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
const cppbor::Array* arrayItem = item->asArray();
if (arrayItem == nullptr || arrayItem->size() != 3) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
const cppbor::Bool* testCredentialItem =
((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
: nullptr);
const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
if (docTypeItem == nullptr || testCredentialItem == nullptr ||
encryptedCredentialKeysItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
docType = docTypeItem->value();
vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
optional<vector<uint8_t>> decryptedCredentialKeys =
support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
if (!decryptedCredentialKeys) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
if (dckItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
const cppbor::Array* dckArrayItem = dckItem->asArray();
if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
}
storageKey = storageKeyItem->value();
credentialPrivKey = credentialPrivKeyItem->value();
return make_tuple(true, docType, storageKey, credentialPrivKey);
}
TEST_P(IdentityAidl, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
@@ -155,6 +213,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature)
.isOk());
// Validate the proofOfProvisioning which was returned
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
@@ -215,6 +274,22 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
credentialPubKey.value()));
writableCredential = nullptr;
// Extract doctype, storage key, and credentialPrivKey from credentialData... this works
// only because we asked for a test-credential meaning that the HBK is all zeroes.
auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] =
extractFromTestCredentialData(credentialData);
ASSERT_TRUE(exSuccess);
ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl");
// ... check that the public key derived from the private key matches what was
// in the certificate.
optional<vector<uint8_t>> exCredentialKeyPair =
support::ecPrivateKeyToKeyPair(exCredentialPrivKey);
ASSERT_TRUE(exCredentialKeyPair);
optional<vector<uint8_t>> exCredentialPubKey =
support::ecKeyPairGetPublicKey(exCredentialKeyPair.value());
ASSERT_TRUE(exCredentialPubKey);
ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value());
// Now that the credential has been provisioned, read it back and check the
// correct data is returned.
sp<IIdentityCredential> credential;
@@ -287,6 +362,24 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
optional<vector<uint8_t>> signingPubKey =
support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
EXPECT_TRUE(signingPubKey);
// 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
// is in the certificate...
const vector<uint8_t> exDocTypeVec(exDocType.begin(), exDocType.end());
optional<vector<uint8_t>> exSigningPrivKey =
support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec);
ASSERT_TRUE(exSigningPrivKey);
optional<vector<uint8_t>> exSigningKeyPair =
support::ecPrivateKeyToKeyPair(exSigningPrivKey.value());
ASSERT_TRUE(exSigningKeyPair);
optional<vector<uint8_t>> exSigningPubKey =
support::ecKeyPairGetPublicKey(exSigningKeyPair.value());
ASSERT_TRUE(exSigningPubKey);
ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value());
vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
// OK to fail, not available in v1 HAL
@@ -316,6 +409,9 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
content.insert(content.end(), chunk.begin(), chunk.end());
}
EXPECT_EQ(content, entry.valueCbor);
// TODO: also use |exStorageKey| to decrypt data and check it's the same as whatt
// the HAL returns...
}
vector<uint8_t> mac;
@@ -346,15 +442,12 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
deviceAuthentication.add(docType);
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
optional<vector<uint8_t>> signingPublicKey =
support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
EXPECT_TRUE(signingPublicKey);
// Derive the key used for MACing.
optional<vector<uint8_t>> readerEphemeralPrivateKey =
support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
optional<vector<uint8_t>> sharedSecret =
support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value());
support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value());
ASSERT_TRUE(sharedSecret);
vector<uint8_t> salt = {0x00};
vector<uint8_t> info = {};

View File

@@ -69,11 +69,10 @@ TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) {
result = writableCredential->getAttestationCertificate(
attestationApplicationId, attestationChallenge, &attestationCertificate);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
EXPECT_TRUE(test_utils::validateAttestationCertificate(
attestationCertificate, attestationChallenge, attestationApplicationId, hwInfo));
EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
}
TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
@@ -130,6 +129,7 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
// First call should go through
const vector<int32_t> entryCounts = {2, 4};
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(5, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -151,18 +151,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
// Verify minimal number of profile count and entry count
const vector<int32_t> entryCounts = {1, 1};
writableCredential->startPersonalization(1, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) {
Status result;
sp<IWritableIdentityCredential> writableCredential;
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
const vector<int32_t> entryCounts = {0};
writableCredential->startPersonalization(0, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(1, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -174,7 +164,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
// Verify minimal number of profile count and entry count
const vector<int32_t> entryCounts = {1};
writableCredential->startPersonalization(1, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(1, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -186,7 +177,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
// Verify set a large number of profile count and entry count is ok
const vector<int32_t> entryCounts = {3000};
writableCredential->startPersonalization(3500, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(25, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -198,7 +190,8 @@ TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
// Enter mismatched entry and profile numbers
const vector<int32_t> entryCounts = {5, 6};
writableCredential->startPersonalization(5, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(5, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -234,7 +227,8 @@ TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
const vector<int32_t> entryCounts = {3, 6};
writableCredential->startPersonalization(3, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -251,9 +245,10 @@ TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
SecureAccessControlProfile profile;
Certificate cert;
cert.encodedCertificate = testProfile.readerCertificate;
int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0;
result = writableCredential->addAccessControlProfile(
testProfile.id, cert, testProfile.userAuthenticationRequired,
testProfile.timeoutMillis, 0, &profile);
testProfile.timeoutMillis, secureUserId, &profile);
if (expectOk) {
expectOk = false;
@@ -554,7 +549,7 @@ TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) {
;
// OK to fail, not available in v1 HAL
writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
writableCredential->startPersonalization(3, entryCounts);
result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -608,7 +603,8 @@ TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
// before "Image" and 2 after image, which is not correct. All of same name
// space should occur together. Let's see if this fails.
const vector<int32_t> entryCounts = {2u, 1u, 2u};
writableCredential->startPersonalization(3, entryCounts);
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -674,6 +670,7 @@ TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) {
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
const vector<int32_t> entryCounts = {1};
writableCredential->setExpectedProofOfProvisioningSize(123456);
Status result = writableCredential->startPersonalization(1, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;

View File

@@ -96,9 +96,10 @@ optional<vector<SecureAccessControlProfile>> addAccessControlProfiles(
SecureAccessControlProfile profile;
Certificate cert;
cert.encodedCertificate = testProfile.readerCertificate;
int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0;
result = writableCredential->addAccessControlProfile(
testProfile.id, cert, testProfile.userAuthenticationRequired,
testProfile.timeoutMillis, 0, &profile);
testProfile.timeoutMillis, secureUserId, &profile);
// Don't use assert so all errors can be outputed. Then return
// instead of exit even on errors so caller can decide.

View File

@@ -134,6 +134,11 @@ optional<vector<uint8_t>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair);
//
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..
//
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
// 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

View File

@@ -1047,6 +1047,42 @@ optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair)
return privateKey;
}
optional<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& privateKey) {
auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr));
if (bn.get() == nullptr) {
LOG(ERROR) << "Error creating BIGNUM";
return {};
}
auto ecKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
if (EC_KEY_set_private_key(ecKey.get(), bn.get()) != 1) {
LOG(ERROR) << "Error setting private key from BIGNUM";
return {};
}
auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
if (pkey.get() == nullptr) {
LOG(ERROR) << "Memory allocation failed";
return {};
}
if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
LOG(ERROR) << "Error getting private key";
return {};
}
int size = i2d_PrivateKey(pkey.get(), nullptr);
if (size == 0) {
LOG(ERROR) << "Error generating public key encoding";
return {};
}
vector<uint8_t> keyPair;
keyPair.resize(size);
unsigned char* p = keyPair.data();
i2d_PrivateKey(pkey.get(), &p);
return keyPair;
}
optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, const string& name,
const string& serialDecimal, const string& issuer,
const string& subject, time_t validityNotBefore,