mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:50:18 +00:00
Merge "identity: Add support for ECDSA auth and don't require session encryption." am: 4c02ef2b4e
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2241333 Change-Id: Ia055667f3967492be14a7bcf0f330d7399f18711 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -18,7 +18,7 @@ aidl_interface {
|
||||
"android.hardware.security.rkp-V3",
|
||||
],
|
||||
stability: "vintf",
|
||||
frozen: true,
|
||||
frozen: false,
|
||||
backend: {
|
||||
java: {
|
||||
platform_apis: true,
|
||||
@@ -67,20 +67,20 @@ aidl_interface {
|
||||
cc_defaults {
|
||||
name: "identity_use_latest_hal_aidl_ndk_static",
|
||||
static_libs: [
|
||||
"android.hardware.identity-V4-ndk",
|
||||
"android.hardware.identity-V5-ndk",
|
||||
],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "identity_use_latest_hal_aidl_ndk_shared",
|
||||
shared_libs: [
|
||||
"android.hardware.identity-V4-ndk",
|
||||
"android.hardware.identity-V5-ndk",
|
||||
],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "identity_use_latest_hal_aidl_cpp_static",
|
||||
static_libs: [
|
||||
"android.hardware.identity-V4-cpp",
|
||||
"android.hardware.identity-V5-cpp",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -51,4 +51,5 @@ interface IIdentityCredential {
|
||||
byte[] deleteCredentialWithChallenge(in byte[] challenge);
|
||||
byte[] proveOwnership(in byte[] challenge);
|
||||
android.hardware.identity.IWritableIdentityCredential updateCredential();
|
||||
@SuppressWarnings(value={"out-array"}) void finishRetrievalWithSignature(out byte[] mac, out byte[] deviceNameSpaces, out byte[] ecdsaSignature);
|
||||
}
|
||||
|
||||
@@ -194,7 +194,8 @@ interface IIdentityCredential {
|
||||
* is permissible for this to be empty in which case the readerSignature parameter
|
||||
* must also be empty. If this is not the case, the call fails with STATUS_FAILED.
|
||||
*
|
||||
* If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public
|
||||
* If mdoc session encryption is used (e.g. createEphemeralKeyPair() has been called)
|
||||
* and 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 the CBOR. Each of these coordinates must appear encoded
|
||||
* with the most significant bits first and use the exact amount of bits indicated by
|
||||
@@ -239,8 +240,8 @@ interface IIdentityCredential {
|
||||
* and remove the corresponding requests from the counts.
|
||||
*/
|
||||
void startRetrieval(in SecureAccessControlProfile[] accessControlProfiles,
|
||||
in HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob,
|
||||
in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
|
||||
in HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob,
|
||||
in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
|
||||
|
||||
/**
|
||||
* Starts retrieving an entry, subject to access control requirements. Entries must be
|
||||
@@ -271,8 +272,8 @@ interface IIdentityCredential {
|
||||
* is given and this profile wasn't passed to startRetrieval() this call fails
|
||||
* with STATUS_INVALID_DATA.
|
||||
*/
|
||||
void startRetrieveEntryValue(in @utf8InCpp String nameSpace, in @utf8InCpp String name,
|
||||
in int entrySize, in int[] accessControlProfileIds);
|
||||
void startRetrieveEntryValue(in @utf8InCpp String nameSpace,
|
||||
in @utf8InCpp String name, in int entrySize, in int[] accessControlProfileIds);
|
||||
|
||||
/**
|
||||
* Retrieves an entry value, or part of one, if the entry value is larger than gcmChunkSize.
|
||||
@@ -293,11 +294,13 @@ interface IIdentityCredential {
|
||||
* returned data.
|
||||
*
|
||||
* If signingKeyBlob or the sessionTranscript parameter passed to startRetrieval() is
|
||||
* empty then the returned MAC will be empty.
|
||||
* empty or if mdoc session encryption is not being used (e.g. if createEphemeralKeyPair()
|
||||
* was not called) then the returned MAC will be empty.
|
||||
*
|
||||
* @param out mac is empty if signingKeyBlob or the sessionTranscript passed to
|
||||
* startRetrieval() is empty. Otherwise it is a COSE_Mac0 with empty payload
|
||||
* and the detached content is set to DeviceAuthenticationBytes as defined below.
|
||||
* @param out mac is empty if signingKeyBlob, or the sessionTranscript passed to
|
||||
* startRetrieval() is empty, or if mdoc session encryption is not being used (e.g. if
|
||||
* createEphemeralKeyPair() was not called). Otherwise it is a COSE_Mac0 with empty
|
||||
* payload and the detached content is set to DeviceAuthenticationBytes as defined below.
|
||||
* This code is produced by using the key agreement and key derivation function
|
||||
* from the ciphersuite with the authentication private key and the reader
|
||||
* ephemeral public key to compute a shared message authentication code (MAC)
|
||||
@@ -407,13 +410,13 @@ interface IIdentityCredential {
|
||||
*/
|
||||
void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
|
||||
|
||||
/**
|
||||
* Sets the VerificationToken. This method must be called before startRetrieval() is
|
||||
* called. This token uses the same challenge as returned by createAuthChallenge().
|
||||
*
|
||||
* @param verificationToken
|
||||
* The verification token. This token is only valid if the timestamp field is non-zero.
|
||||
*/
|
||||
/**
|
||||
* Sets the VerificationToken. This method must be called before startRetrieval() is
|
||||
* called. This token uses the same challenge as returned by createAuthChallenge().
|
||||
*
|
||||
* @param verificationToken
|
||||
* The verification token. This token is only valid if the timestamp field is non-zero.
|
||||
*/
|
||||
void setVerificationToken(in VerificationToken verificationToken);
|
||||
|
||||
/**
|
||||
@@ -485,4 +488,20 @@ interface IIdentityCredential {
|
||||
* @return an IWritableIdentityCredential
|
||||
*/
|
||||
IWritableIdentityCredential updateCredential();
|
||||
|
||||
/**
|
||||
* Like finishRetrieval() but also returns an ECDSA signature in addition to the MAC.
|
||||
*
|
||||
* See section 9.1.3.6 of ISO/IEC 18013-5:2021 for details of how the signature is calculated.
|
||||
*
|
||||
* Unlike MACing, an ECDSA signature will be returned even if mdoc session encryption isn't
|
||||
* being used.
|
||||
*
|
||||
* This method was introduced in API version 5.
|
||||
*
|
||||
* @param ecdsaSignature a COSE_Sign1 signature described above.
|
||||
*/
|
||||
@SuppressWarnings(value={"out-array"})
|
||||
void finishRetrievalWithSignature(
|
||||
out byte[] mac, out byte[] deviceNameSpaces, out byte[] ecdsaSignature);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,17 @@ interface IPresentationSession {
|
||||
*
|
||||
* This can be empty but if it's non-empty it must be valid CBOR.
|
||||
*
|
||||
* If mdoc session encryption is used (e.g. getEphemeralKeyPair() has been called)
|
||||
* and 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 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.
|
||||
*
|
||||
* This method may only be called once per instance. If called more than once, STATUS_FAILED
|
||||
* must be returned.
|
||||
*
|
||||
|
||||
@@ -596,10 +596,10 @@ bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() {
|
||||
return eicPresentationStartRetrieveEntries(&ctx_);
|
||||
}
|
||||
|
||||
bool FakeSecureHardwarePresentationProxy::calcMacKey(
|
||||
bool FakeSecureHardwarePresentationProxy::prepareDeviceAuthentication(
|
||||
const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||
unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) {
|
||||
unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
|
||||
if (!validateId(__func__)) {
|
||||
return false;
|
||||
}
|
||||
@@ -608,10 +608,10 @@ bool FakeSecureHardwarePresentationProxy::calcMacKey(
|
||||
eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
|
||||
return false;
|
||||
}
|
||||
return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(),
|
||||
readerEphemeralPublicKey.data(), signingKeyBlob.data(),
|
||||
docType.c_str(), docType.size(), numNamespacesWithValues,
|
||||
expectedProofOfProvisioningSize);
|
||||
return eicPresentationPrepareDeviceAuthentication(
|
||||
&ctx_, sessionTranscript.data(), sessionTranscript.size(),
|
||||
readerEphemeralPublicKey.data(), readerEphemeralPublicKey.size(), signingKeyBlob.data(),
|
||||
docType.c_str(), docType.size(), numNamespacesWithValues, expectedDeviceNamespacesSize);
|
||||
}
|
||||
|
||||
AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
|
||||
@@ -673,6 +673,25 @@ optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValu
|
||||
return content;
|
||||
}
|
||||
|
||||
optional<pair<vector<uint8_t>, vector<uint8_t>>>
|
||||
FakeSecureHardwarePresentationProxy::finishRetrievalWithSignature() {
|
||||
if (!validateId(__func__)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
vector<uint8_t> mac(32);
|
||||
size_t macSize = 32;
|
||||
vector<uint8_t> ecdsaSignature(EIC_ECDSA_P256_SIGNATURE_SIZE);
|
||||
size_t ecdsaSignatureSize = EIC_ECDSA_P256_SIGNATURE_SIZE;
|
||||
if (!eicPresentationFinishRetrievalWithSignature(&ctx_, mac.data(), &macSize,
|
||||
ecdsaSignature.data(), &ecdsaSignatureSize)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
mac.resize(macSize);
|
||||
ecdsaSignature.resize(ecdsaSignatureSize);
|
||||
return std::make_pair(mac, ecdsaSignature);
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
|
||||
if (!validateId(__func__)) {
|
||||
return std::nullopt;
|
||||
|
||||
@@ -175,11 +175,11 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
|
||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||
const vector<uint8_t>& readerSignatureOfToBeSigned) override;
|
||||
|
||||
bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||
unsigned int numNamespacesWithValues,
|
||||
size_t expectedProofOfProvisioningSize) override;
|
||||
bool prepareDeviceAuthentication(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||
unsigned int numNamespacesWithValues,
|
||||
size_t expectedDeviceNamespacesSize) override;
|
||||
|
||||
AccessCheckResult startRetrieveEntryValue(
|
||||
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
||||
@@ -191,6 +191,8 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
|
||||
|
||||
optional<vector<uint8_t>> finishRetrieval() override;
|
||||
|
||||
optional<pair<vector<uint8_t>, vector<uint8_t>>> finishRetrievalWithSignature() override;
|
||||
|
||||
optional<vector<uint8_t>> deleteCredential(const string& docType,
|
||||
const vector<uint8_t>& challenge,
|
||||
bool includeChallenge,
|
||||
|
||||
@@ -457,17 +457,16 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
||||
}
|
||||
|
||||
if (session_) {
|
||||
// If presenting in a session, the TA has already done this check.
|
||||
|
||||
// If presenting in a session, the TA has already done the check for (X, Y) as done
|
||||
// below, see eicSessionSetSessionTranscript().
|
||||
} else {
|
||||
// To prevent replay-attacks, we check that the public part of the ephemeral
|
||||
// key we previously created, is present in the DeviceEngagement part of
|
||||
// SessionTranscript as a COSE_Key, in uncompressed form.
|
||||
// If mdoc session encryption is in use, check that the
|
||||
// public part of the ephemeral key we previously created, is
|
||||
// present in the DeviceEngagement part of SessionTranscript
|
||||
// as a COSE_Key, in uncompressed form.
|
||||
//
|
||||
// We do this by just searching for the X and Y coordinates.
|
||||
//
|
||||
// Would be nice to move this check to the TA.
|
||||
if (sessionTranscript.size() > 0) {
|
||||
if (sessionTranscript.size() > 0 && ephemeralPublicKey_.size() > 0) {
|
||||
auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
|
||||
if (!getXYSuccess) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
@@ -608,33 +607,36 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
||||
// Finally, pass info so the HMAC key can be derived and the TA can start
|
||||
// creating the DeviceNameSpaces CBOR...
|
||||
if (!session_) {
|
||||
if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
|
||||
signingKeyBlob.size() > 0) {
|
||||
// We expect the reader ephemeral public key to be same size and curve
|
||||
// as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
|
||||
// won't work. So its length should be 65 bytes and it should be
|
||||
// starting with 0x04.
|
||||
if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Reader public key is not in expected format"));
|
||||
if (sessionTranscript_.size() > 0 && signingKeyBlob.size() > 0) {
|
||||
vector<uint8_t> eReaderKeyP256;
|
||||
if (readerPublicKey_.size() > 0) {
|
||||
// If set, we expect the reader ephemeral public key to be same size and curve
|
||||
// as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH won't
|
||||
// work. So its length should be 65 bytes and it should be starting with 0x04.
|
||||
if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Reader public key is not in expected format"));
|
||||
}
|
||||
eReaderKeyP256 =
|
||||
vector<uint8_t>(readerPublicKey_.begin() + 1, readerPublicKey_.end());
|
||||
}
|
||||
vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
|
||||
if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
|
||||
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
||||
if (!hwProxy_->prepareDeviceAuthentication(
|
||||
sessionTranscript_, eReaderKeyP256, signingKeyBlob, docType_,
|
||||
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error starting retrieving entries"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (session_->getSessionTranscript().size() > 0 &&
|
||||
session_->getReaderEphemeralPublicKey().size() > 0 && signingKeyBlob.size() > 0) {
|
||||
if (session_->getSessionTranscript().size() > 0 && signingKeyBlob.size() > 0) {
|
||||
// Don't actually pass the reader ephemeral public key in, the TA will get
|
||||
// it from the session object.
|
||||
//
|
||||
if (!hwProxy_->calcMacKey(sessionTranscript_, {}, signingKeyBlob, docType_,
|
||||
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
||||
if (!hwProxy_->prepareDeviceAuthentication(sessionTranscript_, {}, signingKeyBlob,
|
||||
docType_, numNamespacesWithValues,
|
||||
expectedDeviceNameSpacesSize_)) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error starting retrieving entries"));
|
||||
@@ -924,8 +926,9 @@ ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>&
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
vector<uint8_t>* outDeviceNameSpaces) {
|
||||
ndk::ScopedAStatus IdentityCredential::finishRetrievalWithSignature(
|
||||
vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces,
|
||||
vector<uint8_t>* outEcdsaSignature) {
|
||||
ndk::ScopedAStatus status = ensureHwProxy();
|
||||
if (!status.isOk()) {
|
||||
return status;
|
||||
@@ -948,17 +951,34 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
.c_str()));
|
||||
}
|
||||
|
||||
optional<vector<uint8_t>> digestToBeMaced;
|
||||
optional<vector<uint8_t>> signatureToBeSigned;
|
||||
|
||||
// This relies on the fact that binder calls never pass a nullptr
|
||||
// for out parameters. Hence if it's null here we know this was
|
||||
// called from finishRetrieval() below.
|
||||
if (outEcdsaSignature == nullptr) {
|
||||
digestToBeMaced = hwProxy_->finishRetrieval();
|
||||
if (!digestToBeMaced) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_INVALID_DATA,
|
||||
"Error generating digestToBeMaced"));
|
||||
}
|
||||
} else {
|
||||
auto macAndSignature = hwProxy_->finishRetrievalWithSignature();
|
||||
if (!macAndSignature) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_INVALID_DATA,
|
||||
"Error generating digestToBeMaced and signatureToBeSigned"));
|
||||
}
|
||||
digestToBeMaced = macAndSignature->first;
|
||||
signatureToBeSigned = macAndSignature->second;
|
||||
}
|
||||
|
||||
// If the TA calculated a MAC (it might not have), format it as a COSE_Mac0
|
||||
//
|
||||
optional<vector<uint8_t>> mac;
|
||||
optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
|
||||
|
||||
// The MAC not being set means an error occurred.
|
||||
if (!digestToBeMaced) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_INVALID_DATA, "Error generating digestToBeMaced"));
|
||||
}
|
||||
// Size 0 means that the MAC isn't set. If it's set, it has to be 32 bytes.
|
||||
optional<vector<uint8_t>> mac;
|
||||
if (digestToBeMaced.value().size() != 0) {
|
||||
if (digestToBeMaced.value().size() != 32) {
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
@@ -967,12 +987,27 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
}
|
||||
mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
|
||||
}
|
||||
|
||||
*outMac = mac.value_or(vector<uint8_t>({}));
|
||||
|
||||
optional<vector<uint8_t>> signature;
|
||||
if (signatureToBeSigned && signatureToBeSigned.value().size() != 0) {
|
||||
signature = support::coseSignEcDsaWithSignature(signatureToBeSigned.value(), {}, // data
|
||||
{}); // certificateChain
|
||||
}
|
||||
if (outEcdsaSignature != nullptr) {
|
||||
*outEcdsaSignature = signature.value_or(vector<uint8_t>({}));
|
||||
}
|
||||
|
||||
*outDeviceNameSpaces = encodedDeviceNameSpaces;
|
||||
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
||||
vector<uint8_t>* outDeviceNameSpaces) {
|
||||
return finishRetrievalWithSignature(outMac, outDeviceNameSpaces, nullptr);
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
|
||||
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
|
||||
if (session_) {
|
||||
|
||||
@@ -92,6 +92,10 @@ class IdentityCredential : public BnIdentityCredential {
|
||||
ndk::ScopedAStatus updateCredential(
|
||||
shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
|
||||
|
||||
ndk::ScopedAStatus finishRetrievalWithSignature(vector<uint8_t>* outMac,
|
||||
vector<uint8_t>* outDeviceNameSpaces,
|
||||
vector<uint8_t>* outEcdsaSignature) override;
|
||||
|
||||
private:
|
||||
ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
|
||||
bool includeChallenge,
|
||||
|
||||
@@ -54,19 +54,6 @@ int PresentationSession::initialize() {
|
||||
}
|
||||
id_ = id.value();
|
||||
|
||||
optional<vector<uint8_t>> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair();
|
||||
if (!ephemeralKeyPriv) {
|
||||
LOG(ERROR) << "Error getting ephemeral private key for session";
|
||||
return IIdentityCredentialStore::STATUS_FAILED;
|
||||
}
|
||||
optional<vector<uint8_t>> ephemeralKeyPair =
|
||||
support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value());
|
||||
if (!ephemeralKeyPair) {
|
||||
LOG(ERROR) << "Error creating ephemeral key-pair";
|
||||
return IIdentityCredentialStore::STATUS_FAILED;
|
||||
}
|
||||
ephemeralKeyPair_ = ephemeralKeyPair.value();
|
||||
|
||||
optional<uint64_t> authChallenge = hwProxy_->getAuthChallenge();
|
||||
if (!authChallenge) {
|
||||
LOG(ERROR) << "Error getting authChallenge for session";
|
||||
@@ -78,6 +65,23 @@ int PresentationSession::initialize() {
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus PresentationSession::getEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
|
||||
if (ephemeralKeyPair_.size() == 0) {
|
||||
optional<vector<uint8_t>> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair();
|
||||
if (!ephemeralKeyPriv) {
|
||||
LOG(ERROR) << "Error getting ephemeral private key for session";
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED,
|
||||
"Error getting ephemeral private key for session"));
|
||||
}
|
||||
optional<vector<uint8_t>> ephemeralKeyPair =
|
||||
support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value());
|
||||
if (!ephemeralKeyPair) {
|
||||
LOG(ERROR) << "Error creating ephemeral key-pair";
|
||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||
IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
|
||||
}
|
||||
ephemeralKeyPair_ = ephemeralKeyPair.value();
|
||||
}
|
||||
*outKeyPair = ephemeralKeyPair_;
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
@@ -72,9 +72,11 @@ class PresentationSession : public BnPresentationSession {
|
||||
|
||||
// Set by initialize()
|
||||
uint64_t id_;
|
||||
vector<uint8_t> ephemeralKeyPair_;
|
||||
uint64_t authChallenge_;
|
||||
|
||||
// Set by getEphemeralKeyPair()
|
||||
vector<uint8_t> ephemeralKeyPair_;
|
||||
|
||||
// Set by setReaderEphemeralPublicKey()
|
||||
vector<uint8_t> readerPublicKey_;
|
||||
|
||||
|
||||
@@ -194,11 +194,12 @@ class SecureHardwarePresentationProxy : public RefBase {
|
||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||
const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
|
||||
|
||||
virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||
unsigned int numNamespacesWithValues,
|
||||
size_t expectedProofOfProvisioningSize) = 0;
|
||||
virtual bool prepareDeviceAuthentication(const vector<uint8_t>& sessionTranscript,
|
||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||
const vector<uint8_t>& signingKeyBlob,
|
||||
const string& docType,
|
||||
unsigned int numNamespacesWithValues,
|
||||
size_t expectedDeviceNamespacesSize) = 0;
|
||||
|
||||
virtual AccessCheckResult startRetrieveEntryValue(
|
||||
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
||||
@@ -209,6 +210,7 @@ class SecureHardwarePresentationProxy : public RefBase {
|
||||
const vector<int32_t>& accessControlProfileIds) = 0;
|
||||
|
||||
virtual optional<vector<uint8_t>> finishRetrieval();
|
||||
virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> finishRetrievalWithSignature();
|
||||
|
||||
virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
|
||||
const vector<uint8_t>& challenge,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<manifest version="1.0" type="device">
|
||||
<hal format="aidl">
|
||||
<name>android.hardware.identity</name>
|
||||
<version>4</version>
|
||||
<version>5</version>
|
||||
<interface>
|
||||
<name>IIdentityCredentialStore</name>
|
||||
<instance>default</instance>
|
||||
|
||||
@@ -557,87 +557,11 @@ bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
|
||||
size_t sessionTranscriptSize,
|
||||
const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
|
||||
const uint8_t signingKeyBlob[60], const char* docType,
|
||||
size_t docTypeLength, unsigned int numNamespacesWithValues,
|
||||
size_t expectedDeviceNamespacesSize) {
|
||||
if (ctx->sessionId != 0) {
|
||||
EicSession* session = eicSessionGetForId(ctx->sessionId);
|
||||
if (session == NULL) {
|
||||
eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId);
|
||||
return false;
|
||||
}
|
||||
EicSha256Ctx sha256;
|
||||
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
|
||||
eicOpsSha256Init(&sha256);
|
||||
eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize);
|
||||
eicOpsSha256Final(&sha256, sessionTranscriptSha256);
|
||||
if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256,
|
||||
EIC_SHA256_DIGEST_SIZE) != 0) {
|
||||
eicDebug("SessionTranscript mismatch");
|
||||
return false;
|
||||
}
|
||||
readerEphemeralPublicKey = session->readerEphemeralPublicKey;
|
||||
}
|
||||
|
||||
uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
|
||||
if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
|
||||
docTypeLength, signingKeyPriv)) {
|
||||
eicDebug("Error decrypting signingKeyBlob");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
|
||||
if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
|
||||
eicDebug("ECDH failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
EicCbor cbor;
|
||||
eicCborInit(&cbor, NULL, 0);
|
||||
eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
|
||||
uint8_t salt[EIC_SHA256_DIGEST_SIZE];
|
||||
eicCborFinal(&cbor, salt);
|
||||
|
||||
const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
|
||||
uint8_t derivedKey[32];
|
||||
if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info),
|
||||
derivedKey, sizeof(derivedKey))) {
|
||||
eicDebug("HKDF failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
|
||||
ctx->buildCbor = true;
|
||||
|
||||
// What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
|
||||
// structure which looks like the following:
|
||||
//
|
||||
// MAC_structure = [
|
||||
// context : "MAC" / "MAC0",
|
||||
// protected : empty_or_serialized_map,
|
||||
// external_aad : bstr,
|
||||
// payload : bstr
|
||||
// ]
|
||||
//
|
||||
eicCborAppendArray(&ctx->cbor, 4);
|
||||
eicCborAppendStringZ(&ctx->cbor, "MAC0");
|
||||
|
||||
// The COSE Encoded protected headers is just a single field with
|
||||
// COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
|
||||
// hard-code the CBOR encoding:
|
||||
static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
|
||||
eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
|
||||
sizeof(coseEncodedProtectedHeaders));
|
||||
|
||||
// We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
|
||||
// so external_aad is the empty bstr
|
||||
static const uint8_t externalAad[0] = {};
|
||||
eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
|
||||
|
||||
// Helper used to append the DeviceAuthencation prelude, used for both MACing and ECDSA signing.
|
||||
static size_t appendDeviceAuthentication(EicCbor* cbor, const uint8_t* sessionTranscript,
|
||||
size_t sessionTranscriptSize, const char* docType,
|
||||
size_t docTypeLength,
|
||||
size_t expectedDeviceNamespacesSize) {
|
||||
// For the payload, the _encoded_ form follows here. We handle this by simply
|
||||
// opening a bstr, and then writing the CBOR. This requires us to know the
|
||||
// size of said bstr, ahead of time... the CBOR to be written is
|
||||
@@ -674,26 +598,148 @@ bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTrans
|
||||
dabCalculatedSize += calculatedSize;
|
||||
|
||||
// Begin the bytestring for DeviceAuthenticationBytes;
|
||||
eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
|
||||
eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
|
||||
|
||||
eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
eicCborAppendSemantic(cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
|
||||
// Begins the bytestring for DeviceAuthentication;
|
||||
eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
|
||||
eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
|
||||
|
||||
eicCborAppendArray(&ctx->cbor, 4);
|
||||
eicCborAppendStringZ(&ctx->cbor, "DeviceAuthentication");
|
||||
eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
|
||||
eicCborAppendString(&ctx->cbor, docType, docTypeLength);
|
||||
eicCborAppendArray(cbor, 4);
|
||||
eicCborAppendStringZ(cbor, "DeviceAuthentication");
|
||||
eicCborAppend(cbor, sessionTranscript, sessionTranscriptSize);
|
||||
eicCborAppendString(cbor, docType, docTypeLength);
|
||||
|
||||
// For the payload, the _encoded_ form follows here. We handle this by simply
|
||||
// opening a bstr, and then writing the CBOR. This requires us to know the
|
||||
// size of said bstr, ahead of time.
|
||||
eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
|
||||
ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
|
||||
eicCborAppendSemantic(cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
|
||||
size_t expectedCborSizeAtEnd = expectedDeviceNamespacesSize + cbor->size;
|
||||
|
||||
return expectedCborSizeAtEnd;
|
||||
}
|
||||
|
||||
bool eicPresentationPrepareDeviceAuthentication(
|
||||
EicPresentation* ctx, const uint8_t* sessionTranscript, size_t sessionTranscriptSize,
|
||||
const uint8_t* readerEphemeralPublicKey, size_t readerEphemeralPublicKeySize,
|
||||
const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
|
||||
unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
|
||||
if (ctx->sessionId != 0) {
|
||||
if (readerEphemeralPublicKeySize != 0) {
|
||||
eicDebug("In a session but readerEphemeralPublicKeySize is non-zero");
|
||||
return false;
|
||||
}
|
||||
EicSession* session = eicSessionGetForId(ctx->sessionId);
|
||||
if (session == NULL) {
|
||||
eicDebug("Error looking up session for sessionId %" PRIu32, ctx->sessionId);
|
||||
return false;
|
||||
}
|
||||
EicSha256Ctx sha256;
|
||||
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
|
||||
eicOpsSha256Init(&sha256);
|
||||
eicOpsSha256Update(&sha256, sessionTranscript, sessionTranscriptSize);
|
||||
eicOpsSha256Final(&sha256, sessionTranscriptSha256);
|
||||
if (eicCryptoMemCmp(sessionTranscriptSha256, session->sessionTranscriptSha256,
|
||||
EIC_SHA256_DIGEST_SIZE) != 0) {
|
||||
eicDebug("SessionTranscript mismatch");
|
||||
return false;
|
||||
}
|
||||
readerEphemeralPublicKey = session->readerEphemeralPublicKey;
|
||||
readerEphemeralPublicKeySize = session->readerEphemeralPublicKeySize;
|
||||
}
|
||||
|
||||
// Stash the decrypted DeviceKey in context since we'll need it later in
|
||||
// eicPresentationFinishRetrievalWithSignature()
|
||||
if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
|
||||
docTypeLength, ctx->deviceKeyPriv)) {
|
||||
eicDebug("Error decrypting signingKeyBlob");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can only do MACing if EReaderKey has been set... it might not have been set if for
|
||||
// example mdoc session encryption isn't in use. In that case we can still do ECDSA
|
||||
if (readerEphemeralPublicKeySize > 0) {
|
||||
if (readerEphemeralPublicKeySize != EIC_P256_PUB_KEY_SIZE) {
|
||||
eicDebug("Unexpected size %zd for readerEphemeralPublicKeySize",
|
||||
readerEphemeralPublicKeySize);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
|
||||
if (!eicOpsEcdh(readerEphemeralPublicKey, ctx->deviceKeyPriv, sharedSecret)) {
|
||||
eicDebug("ECDH failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
EicCbor cbor;
|
||||
eicCborInit(&cbor, NULL, 0);
|
||||
eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||
eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
|
||||
uint8_t salt[EIC_SHA256_DIGEST_SIZE];
|
||||
eicCborFinal(&cbor, salt);
|
||||
|
||||
const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
|
||||
uint8_t derivedKey[32];
|
||||
if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info,
|
||||
sizeof(info), derivedKey, sizeof(derivedKey))) {
|
||||
eicDebug("HKDF failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
|
||||
|
||||
// What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
|
||||
// structure which looks like the following:
|
||||
//
|
||||
// MAC_structure = [
|
||||
// context : "MAC" / "MAC0",
|
||||
// protected : empty_or_serialized_map,
|
||||
// external_aad : bstr,
|
||||
// payload : bstr
|
||||
// ]
|
||||
//
|
||||
eicCborAppendArray(&ctx->cbor, 4);
|
||||
eicCborAppendStringZ(&ctx->cbor, "MAC0");
|
||||
|
||||
// The COSE Encoded protected headers is just a single field with
|
||||
// COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
|
||||
// hard-code the CBOR encoding:
|
||||
static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
|
||||
eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
|
||||
sizeof(coseEncodedProtectedHeaders));
|
||||
|
||||
// We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
|
||||
// so external_aad is the empty bstr
|
||||
static const uint8_t externalAad[0] = {};
|
||||
eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
|
||||
|
||||
// Append DeviceAuthentication prelude and open the DeviceSigned map...
|
||||
ctx->expectedCborSizeAtEnd =
|
||||
appendDeviceAuthentication(&ctx->cbor, sessionTranscript, sessionTranscriptSize,
|
||||
docType, docTypeLength, expectedDeviceNamespacesSize);
|
||||
eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
|
||||
ctx->buildCbor = true;
|
||||
}
|
||||
|
||||
// Now do the same for ECDSA signatures...
|
||||
//
|
||||
eicCborInit(&ctx->cborEcdsa, NULL, 0);
|
||||
eicCborAppendArray(&ctx->cborEcdsa, 4);
|
||||
eicCborAppendStringZ(&ctx->cborEcdsa, "Signature1");
|
||||
static const uint8_t coseEncodedProtectedHeadersEcdsa[] = {0xa1, 0x01, 0x26};
|
||||
eicCborAppendByteString(&ctx->cborEcdsa, coseEncodedProtectedHeadersEcdsa,
|
||||
sizeof(coseEncodedProtectedHeadersEcdsa));
|
||||
static const uint8_t externalAadEcdsa[0] = {};
|
||||
eicCborAppendByteString(&ctx->cborEcdsa, externalAadEcdsa, sizeof(externalAadEcdsa));
|
||||
|
||||
// Append DeviceAuthentication prelude and open the DeviceSigned map...
|
||||
ctx->expectedCborEcdsaSizeAtEnd =
|
||||
appendDeviceAuthentication(&ctx->cborEcdsa, sessionTranscript, sessionTranscriptSize,
|
||||
docType, docTypeLength, expectedDeviceNamespacesSize);
|
||||
eicCborAppendMap(&ctx->cborEcdsa, numNamespacesWithValues);
|
||||
ctx->buildCborEcdsa = true;
|
||||
|
||||
eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -702,6 +748,7 @@ bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
|
||||
// state objects here.
|
||||
ctx->requestMessageValidated = false;
|
||||
ctx->buildCbor = false;
|
||||
ctx->buildCborEcdsa = false;
|
||||
ctx->accessControlProfileMaskValidated = 0;
|
||||
ctx->accessControlProfileMaskUsesReaderAuth = 0;
|
||||
ctx->accessControlProfileMaskFailedReaderAuth = 0;
|
||||
@@ -724,6 +771,9 @@ EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
|
||||
if (newNamespaceNumEntries > 0) {
|
||||
eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
|
||||
eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
|
||||
|
||||
eicCborAppendString(&ctx->cborEcdsa, nameSpace, nameSpaceLength);
|
||||
eicCborAppendMap(&ctx->cborEcdsa, newNamespaceNumEntries);
|
||||
}
|
||||
|
||||
// We'll need to calc and store a digest of additionalData to check that it's the same
|
||||
@@ -778,6 +828,7 @@ EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
|
||||
|
||||
if (result == EIC_ACCESS_CHECK_RESULT_OK) {
|
||||
eicCborAppendString(&ctx->cbor, name, nameLength);
|
||||
eicCborAppendString(&ctx->cborEcdsa, name, nameLength);
|
||||
ctx->accessCheckOk = true;
|
||||
}
|
||||
return result;
|
||||
@@ -821,6 +872,7 @@ bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encr
|
||||
}
|
||||
|
||||
eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
|
||||
eicCborAppend(&ctx->cborEcdsa, content, encryptedContentSize - 28);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -842,6 +894,40 @@ bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMac
|
||||
return false;
|
||||
}
|
||||
eicCborFinal(&ctx->cbor, digestToBeMaced);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool eicPresentationFinishRetrievalWithSignature(EicPresentation* ctx, uint8_t* digestToBeMaced,
|
||||
size_t* digestToBeMacedSize,
|
||||
uint8_t* signatureOfToBeSigned,
|
||||
size_t* signatureOfToBeSignedSize) {
|
||||
if (!eicPresentationFinishRetrieval(ctx, digestToBeMaced, digestToBeMacedSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ctx->buildCborEcdsa) {
|
||||
*signatureOfToBeSignedSize = 0;
|
||||
return true;
|
||||
}
|
||||
if (*signatureOfToBeSignedSize != EIC_ECDSA_P256_SIGNATURE_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This verifies that the correct expectedDeviceNamespacesSize value was
|
||||
// passed in at eicPresentationCalcMacKey() time.
|
||||
if (ctx->cborEcdsa.size != ctx->expectedCborEcdsaSizeAtEnd) {
|
||||
eicDebug("CBOR ECDSA size is %zd, was expecting %zd", ctx->cborEcdsa.size,
|
||||
ctx->expectedCborEcdsaSizeAtEnd);
|
||||
return false;
|
||||
}
|
||||
uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
|
||||
eicCborFinal(&ctx->cborEcdsa, cborSha256);
|
||||
if (!eicOpsEcDsa(ctx->deviceKeyPriv, cborSha256, signatureOfToBeSigned)) {
|
||||
eicDebug("Error signing DeviceAuthentication");
|
||||
return false;
|
||||
}
|
||||
eicDebug("set the signature");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ typedef struct {
|
||||
// aren't.
|
||||
bool requestMessageValidated;
|
||||
bool buildCbor;
|
||||
bool buildCborEcdsa;
|
||||
|
||||
// Set to true initialized as a test credential.
|
||||
bool testCredential;
|
||||
@@ -101,6 +102,12 @@ typedef struct {
|
||||
|
||||
size_t expectedCborSizeAtEnd;
|
||||
EicCbor cbor;
|
||||
|
||||
// The selected DeviceKey / AuthKey
|
||||
uint8_t deviceKeyPriv[EIC_P256_PRIV_KEY_SIZE];
|
||||
|
||||
EicCbor cborEcdsa;
|
||||
size_t expectedCborEcdsaSizeAtEnd;
|
||||
} EicPresentation;
|
||||
|
||||
// If sessionId is zero (EIC_PRESENTATION_ID_UNSET), the presentation object is not associated
|
||||
@@ -214,14 +221,13 @@ typedef enum {
|
||||
EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
|
||||
} EicAccessCheckResult;
|
||||
|
||||
// Passes enough information to calculate the MACing key
|
||||
// Passes enough information to calculate the MACing key and/or prepare ECDSA signing
|
||||
//
|
||||
bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
|
||||
size_t sessionTranscriptSize,
|
||||
const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
|
||||
const uint8_t signingKeyBlob[60], const char* docType,
|
||||
size_t docTypeLength, unsigned int numNamespacesWithValues,
|
||||
size_t expectedDeviceNamespacesSize);
|
||||
bool eicPresentationPrepareDeviceAuthentication(
|
||||
EicPresentation* ctx, const uint8_t* sessionTranscript, size_t sessionTranscriptSize,
|
||||
const uint8_t* readerEphemeralPublicKey, size_t readerEphemeralPublicKeySize,
|
||||
const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
|
||||
unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize);
|
||||
|
||||
// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
|
||||
// bytes, the bigger the better). It's done this way to avoid allocating stack
|
||||
@@ -253,6 +259,13 @@ bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encr
|
||||
bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
|
||||
size_t* digestToBeMacedSize);
|
||||
|
||||
// Like eicPresentationFinishRetrieval() but also returns an ECDSA signature.
|
||||
//
|
||||
bool eicPresentationFinishRetrievalWithSignature(EicPresentation* ctx, uint8_t* digestToBeMaced,
|
||||
size_t* digestToBeMacedSize,
|
||||
uint8_t* signatureOfToBeSigned,
|
||||
size_t* signatureOfToBeSignedSize);
|
||||
|
||||
// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
|
||||
// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
|
||||
// where content is set to the ProofOfDeletion CBOR.
|
||||
|
||||
@@ -84,30 +84,35 @@ bool eicSessionGetAuthChallenge(EicSession* ctx, uint64_t* outAuthChallenge) {
|
||||
bool eicSessionGetEphemeralKeyPair(EicSession* ctx,
|
||||
uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
|
||||
eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
|
||||
ctx->getEphemeralKeyPairCalled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool eicSessionSetReaderEphemeralPublicKey(
|
||||
EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]) {
|
||||
eicMemCpy(ctx->readerEphemeralPublicKey, readerEphemeralPublicKey, EIC_P256_PUB_KEY_SIZE);
|
||||
ctx->readerEphemeralPublicKeySize = EIC_P256_PUB_KEY_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript,
|
||||
size_t sessionTranscriptSize) {
|
||||
// Only accept the SessionTranscript if X and Y from the ephemeral key
|
||||
// we created is somewhere in SessionTranscript...
|
||||
// If mdoc session encryption is in use, only accept the
|
||||
// SessionTranscript if X and Y from the ephemeral key we created
|
||||
// is somewhere in SessionTranscript...
|
||||
//
|
||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize, ctx->ephemeralPublicKey,
|
||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||
eicDebug("Error finding X from ephemeralPublicKey in sessionTranscript");
|
||||
return false;
|
||||
}
|
||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize,
|
||||
ctx->ephemeralPublicKey + EIC_P256_PUB_KEY_SIZE / 2,
|
||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||
eicDebug("Error finding Y from ephemeralPublicKey in sessionTranscript");
|
||||
return false;
|
||||
if (ctx->getEphemeralKeyPairCalled) {
|
||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize, ctx->ephemeralPublicKey,
|
||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||
eicDebug("Error finding X from ephemeralPublicKey in sessionTranscript");
|
||||
return false;
|
||||
}
|
||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize,
|
||||
ctx->ephemeralPublicKey + EIC_P256_PUB_KEY_SIZE / 2,
|
||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||
eicDebug("Error finding Y from ephemeralPublicKey in sessionTranscript");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// To save space we only store the SHA-256 of SessionTranscript
|
||||
|
||||
@@ -31,6 +31,9 @@ typedef struct {
|
||||
// A non-zero number unique for this EicSession instance
|
||||
uint32_t id;
|
||||
|
||||
// Set to true iff eicSessionGetEphemeralKeyPair() has been called.
|
||||
bool getEphemeralKeyPairCalled;
|
||||
|
||||
// The challenge generated at construction time by eicSessionInit().
|
||||
uint64_t authChallenge;
|
||||
|
||||
@@ -41,6 +44,7 @@ typedef struct {
|
||||
|
||||
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
|
||||
|
||||
size_t readerEphemeralPublicKeySize;
|
||||
} EicSession;
|
||||
|
||||
bool eicSessionInit(EicSession* ctx);
|
||||
|
||||
@@ -441,8 +441,18 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
}
|
||||
|
||||
vector<uint8_t> mac;
|
||||
vector<uint8_t> ecdsaSignature;
|
||||
vector<uint8_t> deviceNameSpacesEncoded;
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
// API version 5 (feature version 202301) returns both MAC and ECDSA signature.
|
||||
if (halApiVersion_ >= 5) {
|
||||
ASSERT_TRUE(credential
|
||||
->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
|
||||
&ecdsaSignature)
|
||||
.isOk());
|
||||
ASSERT_GT(ecdsaSignature.size(), 0);
|
||||
} else {
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
}
|
||||
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ(
|
||||
"{\n"
|
||||
@@ -475,6 +485,21 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
|
||||
if (ecdsaSignature.size() > 0) {
|
||||
vector<uint8_t> encodedDeviceAuthentication =
|
||||
cppbor::Array()
|
||||
.add("DeviceAuthentication")
|
||||
.add(sessionTranscript.clone())
|
||||
.add(docType)
|
||||
.add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
|
||||
.encode();
|
||||
vector<uint8_t> deviceAuthenticationBytes =
|
||||
cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
|
||||
EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
|
||||
deviceAuthenticationBytes, // Detached content
|
||||
signingPubKey.value()));
|
||||
}
|
||||
|
||||
// Also perform an additional empty request. This is what mDL applications
|
||||
// are envisioned to do - one call to get the data elements, another to get
|
||||
// an empty DeviceSignedItems and corresponding MAC.
|
||||
@@ -486,7 +511,16 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||
testEntriesEntryCounts)
|
||||
.isOk());
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
// API version 5 (feature version 202301) returns both MAC and ECDSA signature.
|
||||
if (halApiVersion_ >= 5) {
|
||||
ASSERT_TRUE(credential
|
||||
->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
|
||||
&ecdsaSignature)
|
||||
.isOk());
|
||||
ASSERT_GT(ecdsaSignature.size(), 0);
|
||||
} else {
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
}
|
||||
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ("{}", cborPretty);
|
||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||
@@ -497,6 +531,21 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
|
||||
if (ecdsaSignature.size() > 0) {
|
||||
vector<uint8_t> encodedDeviceAuthentication =
|
||||
cppbor::Array()
|
||||
.add("DeviceAuthentication")
|
||||
.add(sessionTranscript.clone())
|
||||
.add(docType)
|
||||
.add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
|
||||
.encode();
|
||||
vector<uint8_t> deviceAuthenticationBytes =
|
||||
cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
|
||||
EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
|
||||
deviceAuthenticationBytes, // Detached content
|
||||
signingPubKey.value()));
|
||||
}
|
||||
|
||||
// Some mDL apps might send a request but with a single empty
|
||||
// namespace. Check that too.
|
||||
RequestNamespace emptyRequestNS;
|
||||
@@ -508,7 +557,16 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||
testEntriesEntryCounts)
|
||||
.isOk());
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
// API version 5 (feature version 202301) returns both MAC and ECDSA signature.
|
||||
if (halApiVersion_ >= 5) {
|
||||
ASSERT_TRUE(credential
|
||||
->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
|
||||
&ecdsaSignature)
|
||||
.isOk());
|
||||
ASSERT_GT(ecdsaSignature.size(), 0);
|
||||
} else {
|
||||
ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
|
||||
}
|
||||
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ("{}", cborPretty);
|
||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||
@@ -518,6 +576,248 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
||||
eMacKey.value()); // EMacKey
|
||||
ASSERT_TRUE(calculatedMac);
|
||||
EXPECT_EQ(mac, calculatedMac);
|
||||
|
||||
if (ecdsaSignature.size() > 0) {
|
||||
vector<uint8_t> encodedDeviceAuthentication =
|
||||
cppbor::Array()
|
||||
.add("DeviceAuthentication")
|
||||
.add(sessionTranscript.clone())
|
||||
.add(docType)
|
||||
.add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
|
||||
.encode();
|
||||
vector<uint8_t> deviceAuthenticationBytes =
|
||||
cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
|
||||
EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
|
||||
deviceAuthenticationBytes, // Detached content
|
||||
signingPubKey.value()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(EndToEndTests, noSessionEncryption) {
|
||||
if (halApiVersion_ < 5) {
|
||||
GTEST_SKIP() << "Need HAL API version 5, have " << halApiVersion_;
|
||||
}
|
||||
|
||||
const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (no authentication)
|
||||
{0, {}, false, 0}};
|
||||
|
||||
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();
|
||||
|
||||
// Here's the actual test data:
|
||||
const vector<test_utils::TestEntryData> testEntries = {
|
||||
{"PersonalData", "Last name", string("Turing"), vector<int32_t>{0}},
|
||||
{"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0}},
|
||||
{"PersonalData", "First name", string("Alan"), vector<int32_t>{0}},
|
||||
};
|
||||
const vector<int32_t> testEntriesEntryCounts = {3};
|
||||
HardwareInformation hwInfo;
|
||||
ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
|
||||
|
||||
string cborPretty;
|
||||
sp<IWritableIdentityCredential> writableCredential;
|
||||
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_,
|
||||
true /* testCredential */));
|
||||
|
||||
string challenge = "attestationChallenge";
|
||||
test_utils::AttestationData attData(writableCredential, challenge,
|
||||
{1} /* atteestationApplicationId */);
|
||||
ASSERT_TRUE(attData.result.isOk())
|
||||
<< attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
|
||||
|
||||
// This is kinda of a hack but we need to give the size of
|
||||
// ProofOfProvisioning that we'll expect to receive.
|
||||
const int32_t expectedProofOfProvisioningSize = 230;
|
||||
// OK to fail, not available in v1 HAL
|
||||
writableCredential->setExpectedProofOfProvisioningSize(expectedProofOfProvisioningSize);
|
||||
ASSERT_TRUE(
|
||||
writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts)
|
||||
.isOk());
|
||||
|
||||
optional<vector<SecureAccessControlProfile>> secureProfiles =
|
||||
test_utils::addAccessControlProfiles(writableCredential, testProfiles);
|
||||
ASSERT_TRUE(secureProfiles);
|
||||
|
||||
// Uses TestEntryData* pointer as key and values are the encrypted blobs. This
|
||||
// is a little hacky but it works well enough.
|
||||
map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
|
||||
|
||||
for (const auto& entry : testEntries) {
|
||||
ASSERT_TRUE(test_utils::addEntry(writableCredential, entry, hwInfo.dataChunkSize,
|
||||
encryptedBlobs, true));
|
||||
}
|
||||
|
||||
vector<uint8_t> credentialData;
|
||||
vector<uint8_t> proofOfProvisioningSignature;
|
||||
ASSERT_TRUE(
|
||||
writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature)
|
||||
.isOk());
|
||||
|
||||
// Validate the proofOfProvisioning which was returned
|
||||
optional<vector<uint8_t>> proofOfProvisioning =
|
||||
support::coseSignGetPayload(proofOfProvisioningSignature);
|
||||
ASSERT_TRUE(proofOfProvisioning);
|
||||
cborPretty = cppbor::prettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
|
||||
EXPECT_EQ(
|
||||
"[\n"
|
||||
" 'ProofOfProvisioning',\n"
|
||||
" 'org.iso.18013-5.2019.mdl',\n"
|
||||
" [\n"
|
||||
" {\n"
|
||||
" 'id' : 0,\n"
|
||||
" },\n"
|
||||
" ],\n"
|
||||
" {\n"
|
||||
" 'PersonalData' : [\n"
|
||||
" {\n"
|
||||
" 'name' : 'Last name',\n"
|
||||
" 'value' : 'Turing',\n"
|
||||
" 'accessControlProfiles' : [0, ],\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" 'name' : 'Birth date',\n"
|
||||
" 'value' : '19120623',\n"
|
||||
" 'accessControlProfiles' : [0, ],\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" 'name' : 'First name',\n"
|
||||
" 'value' : 'Alan',\n"
|
||||
" 'accessControlProfiles' : [0, ],\n"
|
||||
" },\n"
|
||||
" ],\n"
|
||||
" },\n"
|
||||
" true,\n"
|
||||
"]",
|
||||
cborPretty);
|
||||
|
||||
optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
|
||||
attData.attestationCertificate[0].encodedCertificate);
|
||||
ASSERT_TRUE(credentialPubKey);
|
||||
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
|
||||
{}, // Additional data
|
||||
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, exSha256Pop] =
|
||||
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());
|
||||
|
||||
sp<IIdentityCredential> credential;
|
||||
ASSERT_TRUE(credentialStore_
|
||||
->getCredential(
|
||||
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
|
||||
credentialData, &credential)
|
||||
.isOk());
|
||||
ASSERT_NE(credential, nullptr);
|
||||
|
||||
// Calculate sessionTranscript, make something that resembles what you'd use for
|
||||
// an over-the-Internet presentation not using mdoc session encryption.
|
||||
cppbor::Array sessionTranscript =
|
||||
cppbor::Array()
|
||||
.add(cppbor::Null()) // DeviceEngagementBytes isn't used.
|
||||
.add(cppbor::Null()) // EReaderKeyBytes isn't used.
|
||||
.add(cppbor::Array() // Proprietary handover structure follows.
|
||||
.add(cppbor::Tstr("TestHandover"))
|
||||
.add(cppbor::Bstr(vector<uint8_t>{1, 2, 3}))
|
||||
.add(cppbor::Bstr(vector<uint8_t>{9, 8, 7, 6})));
|
||||
vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode();
|
||||
|
||||
// Generate the key that will be used to sign AuthenticatedData.
|
||||
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);
|
||||
test_utils::verifyAuthKeyCertificate(signingKeyCertificate.encodedCertificate);
|
||||
|
||||
vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
|
||||
ASSERT_TRUE(credential->setRequestedNamespaces(requestedNamespaces).isOk());
|
||||
ASSERT_TRUE(credential->setVerificationToken(verificationToken).isOk());
|
||||
Status status = credential->startRetrieval(
|
||||
secureProfiles.value(), authToken, {} /* itemsRequestBytes*/, signingKeyBlob,
|
||||
sessionTranscriptEncoded, {} /* readerSignature */, testEntriesEntryCounts);
|
||||
ASSERT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
|
||||
|
||||
for (const auto& entry : testEntries) {
|
||||
ASSERT_TRUE(credential
|
||||
->startRetrieveEntryValue(entry.nameSpace, entry.name,
|
||||
entry.valueCbor.size(), entry.profileIds)
|
||||
.isOk());
|
||||
|
||||
auto it = encryptedBlobs.find(&entry);
|
||||
ASSERT_NE(it, encryptedBlobs.end());
|
||||
const vector<vector<uint8_t>>& encryptedChunks = it->second;
|
||||
|
||||
vector<uint8_t> content;
|
||||
for (const auto& encryptedChunk : encryptedChunks) {
|
||||
vector<uint8_t> chunk;
|
||||
ASSERT_TRUE(credential->retrieveEntryValue(encryptedChunk, &chunk).isOk());
|
||||
content.insert(content.end(), chunk.begin(), chunk.end());
|
||||
}
|
||||
EXPECT_EQ(content, entry.valueCbor);
|
||||
}
|
||||
|
||||
vector<uint8_t> mac;
|
||||
vector<uint8_t> ecdsaSignature;
|
||||
vector<uint8_t> deviceNameSpacesEncoded;
|
||||
status = credential->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
|
||||
&ecdsaSignature);
|
||||
ASSERT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
|
||||
// MACing should NOT work since we're not using session encryption
|
||||
ASSERT_EQ(0, mac.size());
|
||||
|
||||
// ECDSA signatures should work, however. Check this.
|
||||
ASSERT_GT(ecdsaSignature.size(), 0);
|
||||
|
||||
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||
ASSERT_EQ(
|
||||
"{\n"
|
||||
" 'PersonalData' : {\n"
|
||||
" 'Last name' : 'Turing',\n"
|
||||
" 'Birth date' : '19120623',\n"
|
||||
" 'First name' : 'Alan',\n"
|
||||
" },\n"
|
||||
"}",
|
||||
cborPretty);
|
||||
|
||||
string docType = "org.iso.18013-5.2019.mdl";
|
||||
|
||||
vector<uint8_t> encodedDeviceAuthentication =
|
||||
cppbor::Array()
|
||||
.add("DeviceAuthentication")
|
||||
.add(sessionTranscript.clone())
|
||||
.add(docType)
|
||||
.add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
|
||||
.encode();
|
||||
vector<uint8_t> deviceAuthenticationBytes =
|
||||
cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
|
||||
EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
|
||||
deviceAuthenticationBytes, // Detached content
|
||||
signingPubKey.value()));
|
||||
}
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests);
|
||||
|
||||
Reference in New Issue
Block a user