You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
26 KiB
604 lines
26 KiB
/*
|
|
* 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 "Util.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::SemanticTag(24, deviceEngagementBytes))
|
|
.add(cppbor::SemanticTag(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> encodedReaderAuthentication =
|
|
cppbor::Array()
|
|
.add("ReaderAuthentication")
|
|
.add(sessionTranscript.clone())
|
|
.add(cppbor::SemanticTag(24, itemsRequestBytes))
|
|
.encode();
|
|
vector<uint8_t> encodedReaderAuthenticationBytes =
|
|
cppbor::SemanticTag(24, encodedReaderAuthentication).encode();
|
|
|
|
optional<vector<uint8_t>> readerSignature =
|
|
support::coseSignEcDsa(readerPrivateKey, // private key for reader
|
|
{}, // content
|
|
encodedReaderAuthenticationBytes, // 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::SemanticTag(24, deviceEngagementBytes))
|
|
.add(cppbor::SemanticTag(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> encodedReaderAuthentication =
|
|
cppbor::Array()
|
|
.add("ReaderAuthentication")
|
|
.add(sessionTranscript.clone())
|
|
.add(cppbor::SemanticTag(24, itemsRequestBytes))
|
|
.encode();
|
|
vector<uint8_t> encodedReaderAuthenticationBytes =
|
|
cppbor::SemanticTag(24, encodedReaderAuthentication).encode();
|
|
|
|
vector<vector<uint8_t>> readerCertChain = {cert_reader_SelfSigned_};
|
|
optional<vector<uint8_t>> readerSignature =
|
|
support::coseSignEcDsa(readerPrivateKey_, // private key for reader
|
|
{}, // content
|
|
encodedReaderAuthenticationBytes, // 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());
|
|
}
|
|
|
|
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ReaderAuthTests);
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
Identity, ReaderAuthTests,
|
|
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
|
|
android::PrintInstanceNameToString);
|
|
|
|
} // namespace android::hardware::identity
|