mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-02 17:31:58 +00:00
Merge "identity: Add support for ECDSA auth and don't require session encryption."
This commit is contained in:
@@ -18,7 +18,7 @@ aidl_interface {
|
|||||||
"android.hardware.security.rkp-V3",
|
"android.hardware.security.rkp-V3",
|
||||||
],
|
],
|
||||||
stability: "vintf",
|
stability: "vintf",
|
||||||
frozen: true,
|
frozen: false,
|
||||||
backend: {
|
backend: {
|
||||||
java: {
|
java: {
|
||||||
platform_apis: true,
|
platform_apis: true,
|
||||||
@@ -67,20 +67,20 @@ aidl_interface {
|
|||||||
cc_defaults {
|
cc_defaults {
|
||||||
name: "identity_use_latest_hal_aidl_ndk_static",
|
name: "identity_use_latest_hal_aidl_ndk_static",
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"android.hardware.identity-V4-ndk",
|
"android.hardware.identity-V5-ndk",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_defaults {
|
cc_defaults {
|
||||||
name: "identity_use_latest_hal_aidl_ndk_shared",
|
name: "identity_use_latest_hal_aidl_ndk_shared",
|
||||||
shared_libs: [
|
shared_libs: [
|
||||||
"android.hardware.identity-V4-ndk",
|
"android.hardware.identity-V5-ndk",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_defaults {
|
cc_defaults {
|
||||||
name: "identity_use_latest_hal_aidl_cpp_static",
|
name: "identity_use_latest_hal_aidl_cpp_static",
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"android.hardware.identity-V4-cpp",
|
"android.hardware.identity-V5-cpp",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,5 @@ interface IIdentityCredential {
|
|||||||
byte[] deleteCredentialWithChallenge(in byte[] challenge);
|
byte[] deleteCredentialWithChallenge(in byte[] challenge);
|
||||||
byte[] proveOwnership(in byte[] challenge);
|
byte[] proveOwnership(in byte[] challenge);
|
||||||
android.hardware.identity.IWritableIdentityCredential updateCredential();
|
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
|
* 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.
|
* 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
|
* 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
|
* 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
|
* 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.
|
* and remove the corresponding requests from the counts.
|
||||||
*/
|
*/
|
||||||
void startRetrieval(in SecureAccessControlProfile[] accessControlProfiles,
|
void startRetrieval(in SecureAccessControlProfile[] accessControlProfiles,
|
||||||
in HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob,
|
in HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob,
|
||||||
in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
|
in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts retrieving an entry, subject to access control requirements. Entries must be
|
* 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
|
* is given and this profile wasn't passed to startRetrieval() this call fails
|
||||||
* with STATUS_INVALID_DATA.
|
* with STATUS_INVALID_DATA.
|
||||||
*/
|
*/
|
||||||
void startRetrieveEntryValue(in @utf8InCpp String nameSpace, in @utf8InCpp String name,
|
void startRetrieveEntryValue(in @utf8InCpp String nameSpace,
|
||||||
in int entrySize, in int[] accessControlProfileIds);
|
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.
|
* Retrieves an entry value, or part of one, if the entry value is larger than gcmChunkSize.
|
||||||
@@ -293,11 +294,13 @@ interface IIdentityCredential {
|
|||||||
* returned data.
|
* returned data.
|
||||||
*
|
*
|
||||||
* If signingKeyBlob or the sessionTranscript parameter passed to startRetrieval() is
|
* 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
|
* @param out mac is empty if signingKeyBlob, or the sessionTranscript passed to
|
||||||
* startRetrieval() is empty. Otherwise it is a COSE_Mac0 with empty payload
|
* startRetrieval() is empty, or if mdoc session encryption is not being used (e.g. if
|
||||||
* and the detached content is set to DeviceAuthenticationBytes as defined below.
|
* 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
|
* This code is produced by using the key agreement and key derivation function
|
||||||
* from the ciphersuite with the authentication private key and the reader
|
* from the ciphersuite with the authentication private key and the reader
|
||||||
* ephemeral public key to compute a shared message authentication code (MAC)
|
* ephemeral public key to compute a shared message authentication code (MAC)
|
||||||
@@ -407,13 +410,13 @@ interface IIdentityCredential {
|
|||||||
*/
|
*/
|
||||||
void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
|
void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the VerificationToken. This method must be called before startRetrieval() is
|
* Sets the VerificationToken. This method must be called before startRetrieval() is
|
||||||
* called. This token uses the same challenge as returned by createAuthChallenge().
|
* called. This token uses the same challenge as returned by createAuthChallenge().
|
||||||
*
|
*
|
||||||
* @param verificationToken
|
* @param verificationToken
|
||||||
* The verification token. This token is only valid if the timestamp field is non-zero.
|
* The verification token. This token is only valid if the timestamp field is non-zero.
|
||||||
*/
|
*/
|
||||||
void setVerificationToken(in VerificationToken verificationToken);
|
void setVerificationToken(in VerificationToken verificationToken);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,4 +488,20 @@ interface IIdentityCredential {
|
|||||||
* @return an IWritableIdentityCredential
|
* @return an IWritableIdentityCredential
|
||||||
*/
|
*/
|
||||||
IWritableIdentityCredential updateCredential();
|
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.
|
* 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
|
* This method may only be called once per instance. If called more than once, STATUS_FAILED
|
||||||
* must be returned.
|
* must be returned.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -596,10 +596,10 @@ bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() {
|
|||||||
return eicPresentationStartRetrieveEntries(&ctx_);
|
return eicPresentationStartRetrieveEntries(&ctx_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FakeSecureHardwarePresentationProxy::calcMacKey(
|
bool FakeSecureHardwarePresentationProxy::prepareDeviceAuthentication(
|
||||||
const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
|
const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
|
||||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||||
unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) {
|
unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
|
||||||
if (!validateId(__func__)) {
|
if (!validateId(__func__)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -608,10 +608,10 @@ bool FakeSecureHardwarePresentationProxy::calcMacKey(
|
|||||||
eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
|
eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(),
|
return eicPresentationPrepareDeviceAuthentication(
|
||||||
readerEphemeralPublicKey.data(), signingKeyBlob.data(),
|
&ctx_, sessionTranscript.data(), sessionTranscript.size(),
|
||||||
docType.c_str(), docType.size(), numNamespacesWithValues,
|
readerEphemeralPublicKey.data(), readerEphemeralPublicKey.size(), signingKeyBlob.data(),
|
||||||
expectedProofOfProvisioningSize);
|
docType.c_str(), docType.size(), numNamespacesWithValues, expectedDeviceNamespacesSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
|
AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
|
||||||
@@ -673,6 +673,25 @@ optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValu
|
|||||||
return content;
|
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() {
|
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
|
||||||
if (!validateId(__func__)) {
|
if (!validateId(__func__)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|||||||
@@ -175,11 +175,11 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
|
|||||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||||
const vector<uint8_t>& readerSignatureOfToBeSigned) override;
|
const vector<uint8_t>& readerSignatureOfToBeSigned) override;
|
||||||
|
|
||||||
bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
bool prepareDeviceAuthentication(const vector<uint8_t>& sessionTranscript,
|
||||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
||||||
unsigned int numNamespacesWithValues,
|
unsigned int numNamespacesWithValues,
|
||||||
size_t expectedProofOfProvisioningSize) override;
|
size_t expectedDeviceNamespacesSize) override;
|
||||||
|
|
||||||
AccessCheckResult startRetrieveEntryValue(
|
AccessCheckResult startRetrieveEntryValue(
|
||||||
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
||||||
@@ -191,6 +191,8 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
|
|||||||
|
|
||||||
optional<vector<uint8_t>> finishRetrieval() override;
|
optional<vector<uint8_t>> finishRetrieval() override;
|
||||||
|
|
||||||
|
optional<pair<vector<uint8_t>, vector<uint8_t>>> finishRetrievalWithSignature() override;
|
||||||
|
|
||||||
optional<vector<uint8_t>> deleteCredential(const string& docType,
|
optional<vector<uint8_t>> deleteCredential(const string& docType,
|
||||||
const vector<uint8_t>& challenge,
|
const vector<uint8_t>& challenge,
|
||||||
bool includeChallenge,
|
bool includeChallenge,
|
||||||
|
|||||||
@@ -457,17 +457,16 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (session_) {
|
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 {
|
} else {
|
||||||
// To prevent replay-attacks, we check that the public part of the ephemeral
|
// If mdoc session encryption is in use, check that the
|
||||||
// key we previously created, is present in the DeviceEngagement part of
|
// public part of the ephemeral key we previously created, is
|
||||||
// SessionTranscript as a COSE_Key, in uncompressed form.
|
// 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.
|
// We do this by just searching for the X and Y coordinates.
|
||||||
//
|
if (sessionTranscript.size() > 0 && ephemeralPublicKey_.size() > 0) {
|
||||||
// Would be nice to move this check to the TA.
|
|
||||||
if (sessionTranscript.size() > 0) {
|
|
||||||
auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
|
auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
|
||||||
if (!getXYSuccess) {
|
if (!getXYSuccess) {
|
||||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
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
|
// Finally, pass info so the HMAC key can be derived and the TA can start
|
||||||
// creating the DeviceNameSpaces CBOR...
|
// creating the DeviceNameSpaces CBOR...
|
||||||
if (!session_) {
|
if (!session_) {
|
||||||
if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
|
if (sessionTranscript_.size() > 0 && signingKeyBlob.size() > 0) {
|
||||||
signingKeyBlob.size() > 0) {
|
vector<uint8_t> eReaderKeyP256;
|
||||||
// We expect the reader ephemeral public key to be same size and curve
|
if (readerPublicKey_.size() > 0) {
|
||||||
// as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
|
// If set, we expect the reader ephemeral public key to be same size and curve
|
||||||
// won't work. So its length should be 65 bytes and it should be
|
// as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH won't
|
||||||
// starting with 0x04.
|
// work. So its length should be 65 bytes and it should be starting with 0x04.
|
||||||
if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
|
if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
|
||||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||||
IIdentityCredentialStore::STATUS_FAILED,
|
IIdentityCredentialStore::STATUS_FAILED,
|
||||||
"Reader public key is not in expected format"));
|
"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_->prepareDeviceAuthentication(
|
||||||
if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
|
sessionTranscript_, eReaderKeyP256, signingKeyBlob, docType_,
|
||||||
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
||||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||||
IIdentityCredentialStore::STATUS_FAILED,
|
IIdentityCredentialStore::STATUS_FAILED,
|
||||||
"Error starting retrieving entries"));
|
"Error starting retrieving entries"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (session_->getSessionTranscript().size() > 0 &&
|
if (session_->getSessionTranscript().size() > 0 && signingKeyBlob.size() > 0) {
|
||||||
session_->getReaderEphemeralPublicKey().size() > 0 && signingKeyBlob.size() > 0) {
|
|
||||||
// Don't actually pass the reader ephemeral public key in, the TA will get
|
// Don't actually pass the reader ephemeral public key in, the TA will get
|
||||||
// it from the session object.
|
// it from the session object.
|
||||||
//
|
//
|
||||||
if (!hwProxy_->calcMacKey(sessionTranscript_, {}, signingKeyBlob, docType_,
|
if (!hwProxy_->prepareDeviceAuthentication(sessionTranscript_, {}, signingKeyBlob,
|
||||||
numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
|
docType_, numNamespacesWithValues,
|
||||||
|
expectedDeviceNameSpacesSize_)) {
|
||||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||||
IIdentityCredentialStore::STATUS_FAILED,
|
IIdentityCredentialStore::STATUS_FAILED,
|
||||||
"Error starting retrieving entries"));
|
"Error starting retrieving entries"));
|
||||||
@@ -924,8 +926,9 @@ ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>&
|
|||||||
return ndk::ScopedAStatus::ok();
|
return ndk::ScopedAStatus::ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
ndk::ScopedAStatus IdentityCredential::finishRetrievalWithSignature(
|
||||||
vector<uint8_t>* outDeviceNameSpaces) {
|
vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces,
|
||||||
|
vector<uint8_t>* outEcdsaSignature) {
|
||||||
ndk::ScopedAStatus status = ensureHwProxy();
|
ndk::ScopedAStatus status = ensureHwProxy();
|
||||||
if (!status.isOk()) {
|
if (!status.isOk()) {
|
||||||
return status;
|
return status;
|
||||||
@@ -948,17 +951,34 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
|||||||
.c_str()));
|
.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
|
// 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.
|
// 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() != 0) {
|
||||||
if (digestToBeMaced.value().size() != 32) {
|
if (digestToBeMaced.value().size() != 32) {
|
||||||
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
|
||||||
@@ -967,12 +987,27 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
|
|||||||
}
|
}
|
||||||
mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
|
mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
|
||||||
}
|
}
|
||||||
|
|
||||||
*outMac = mac.value_or(vector<uint8_t>({}));
|
*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;
|
*outDeviceNameSpaces = encodedDeviceNameSpaces;
|
||||||
|
|
||||||
return ndk::ScopedAStatus::ok();
|
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(
|
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
|
||||||
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
|
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
|
||||||
if (session_) {
|
if (session_) {
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ class IdentityCredential : public BnIdentityCredential {
|
|||||||
ndk::ScopedAStatus updateCredential(
|
ndk::ScopedAStatus updateCredential(
|
||||||
shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
|
shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
|
||||||
|
|
||||||
|
ndk::ScopedAStatus finishRetrievalWithSignature(vector<uint8_t>* outMac,
|
||||||
|
vector<uint8_t>* outDeviceNameSpaces,
|
||||||
|
vector<uint8_t>* outEcdsaSignature) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
|
ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
|
||||||
bool includeChallenge,
|
bool includeChallenge,
|
||||||
|
|||||||
@@ -54,19 +54,6 @@ int PresentationSession::initialize() {
|
|||||||
}
|
}
|
||||||
id_ = id.value();
|
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();
|
optional<uint64_t> authChallenge = hwProxy_->getAuthChallenge();
|
||||||
if (!authChallenge) {
|
if (!authChallenge) {
|
||||||
LOG(ERROR) << "Error getting authChallenge for session";
|
LOG(ERROR) << "Error getting authChallenge for session";
|
||||||
@@ -78,6 +65,23 @@ int PresentationSession::initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ndk::ScopedAStatus PresentationSession::getEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
|
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_;
|
*outKeyPair = ephemeralKeyPair_;
|
||||||
return ndk::ScopedAStatus::ok();
|
return ndk::ScopedAStatus::ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,11 @@ class PresentationSession : public BnPresentationSession {
|
|||||||
|
|
||||||
// Set by initialize()
|
// Set by initialize()
|
||||||
uint64_t id_;
|
uint64_t id_;
|
||||||
vector<uint8_t> ephemeralKeyPair_;
|
|
||||||
uint64_t authChallenge_;
|
uint64_t authChallenge_;
|
||||||
|
|
||||||
|
// Set by getEphemeralKeyPair()
|
||||||
|
vector<uint8_t> ephemeralKeyPair_;
|
||||||
|
|
||||||
// Set by setReaderEphemeralPublicKey()
|
// Set by setReaderEphemeralPublicKey()
|
||||||
vector<uint8_t> readerPublicKey_;
|
vector<uint8_t> readerPublicKey_;
|
||||||
|
|
||||||
|
|||||||
@@ -194,11 +194,12 @@ class SecureHardwarePresentationProxy : public RefBase {
|
|||||||
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
const vector<uint8_t>& requestMessage, int coseSignAlg,
|
||||||
const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
|
const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
|
||||||
|
|
||||||
virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
|
virtual bool prepareDeviceAuthentication(const vector<uint8_t>& sessionTranscript,
|
||||||
const vector<uint8_t>& readerEphemeralPublicKey,
|
const vector<uint8_t>& readerEphemeralPublicKey,
|
||||||
const vector<uint8_t>& signingKeyBlob, const string& docType,
|
const vector<uint8_t>& signingKeyBlob,
|
||||||
unsigned int numNamespacesWithValues,
|
const string& docType,
|
||||||
size_t expectedProofOfProvisioningSize) = 0;
|
unsigned int numNamespacesWithValues,
|
||||||
|
size_t expectedDeviceNamespacesSize) = 0;
|
||||||
|
|
||||||
virtual AccessCheckResult startRetrieveEntryValue(
|
virtual AccessCheckResult startRetrieveEntryValue(
|
||||||
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
|
||||||
@@ -209,6 +210,7 @@ class SecureHardwarePresentationProxy : public RefBase {
|
|||||||
const vector<int32_t>& accessControlProfileIds) = 0;
|
const vector<int32_t>& accessControlProfileIds) = 0;
|
||||||
|
|
||||||
virtual optional<vector<uint8_t>> finishRetrieval();
|
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,
|
virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
|
||||||
const vector<uint8_t>& challenge,
|
const vector<uint8_t>& challenge,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest version="1.0" type="device">
|
<manifest version="1.0" type="device">
|
||||||
<hal format="aidl">
|
<hal format="aidl">
|
||||||
<name>android.hardware.identity</name>
|
<name>android.hardware.identity</name>
|
||||||
<version>4</version>
|
<version>5</version>
|
||||||
<interface>
|
<interface>
|
||||||
<name>IIdentityCredentialStore</name>
|
<name>IIdentityCredentialStore</name>
|
||||||
<instance>default</instance>
|
<instance>default</instance>
|
||||||
|
|||||||
@@ -557,87 +557,11 @@ bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
|
// Helper used to append the DeviceAuthencation prelude, used for both MACing and ECDSA signing.
|
||||||
size_t sessionTranscriptSize,
|
static size_t appendDeviceAuthentication(EicCbor* cbor, const uint8_t* sessionTranscript,
|
||||||
const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
|
size_t sessionTranscriptSize, const char* docType,
|
||||||
const uint8_t signingKeyBlob[60], const char* docType,
|
size_t docTypeLength,
|
||||||
size_t docTypeLength, unsigned int numNamespacesWithValues,
|
size_t expectedDeviceNamespacesSize) {
|
||||||
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));
|
|
||||||
|
|
||||||
// For the payload, the _encoded_ form follows here. We handle this by simply
|
// 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
|
// 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
|
// 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;
|
dabCalculatedSize += calculatedSize;
|
||||||
|
|
||||||
// Begin the bytestring for DeviceAuthenticationBytes;
|
// 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;
|
// 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);
|
eicCborAppendArray(cbor, 4);
|
||||||
eicCborAppendStringZ(&ctx->cbor, "DeviceAuthentication");
|
eicCborAppendStringZ(cbor, "DeviceAuthentication");
|
||||||
eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
|
eicCborAppend(cbor, sessionTranscript, sessionTranscriptSize);
|
||||||
eicCborAppendString(&ctx->cbor, docType, docTypeLength);
|
eicCborAppendString(cbor, docType, docTypeLength);
|
||||||
|
|
||||||
// For the payload, the _encoded_ form follows here. We handle this by simply
|
// 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
|
// opening a bstr, and then writing the CBOR. This requires us to know the
|
||||||
// size of said bstr, ahead of time.
|
// size of said bstr, ahead of time.
|
||||||
eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
eicCborAppendSemantic(cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
|
||||||
eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
|
eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
|
||||||
ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +748,7 @@ bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
|
|||||||
// state objects here.
|
// state objects here.
|
||||||
ctx->requestMessageValidated = false;
|
ctx->requestMessageValidated = false;
|
||||||
ctx->buildCbor = false;
|
ctx->buildCbor = false;
|
||||||
|
ctx->buildCborEcdsa = false;
|
||||||
ctx->accessControlProfileMaskValidated = 0;
|
ctx->accessControlProfileMaskValidated = 0;
|
||||||
ctx->accessControlProfileMaskUsesReaderAuth = 0;
|
ctx->accessControlProfileMaskUsesReaderAuth = 0;
|
||||||
ctx->accessControlProfileMaskFailedReaderAuth = 0;
|
ctx->accessControlProfileMaskFailedReaderAuth = 0;
|
||||||
@@ -724,6 +771,9 @@ EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
|
|||||||
if (newNamespaceNumEntries > 0) {
|
if (newNamespaceNumEntries > 0) {
|
||||||
eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
|
eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
|
||||||
eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
|
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
|
// 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) {
|
if (result == EIC_ACCESS_CHECK_RESULT_OK) {
|
||||||
eicCborAppendString(&ctx->cbor, name, nameLength);
|
eicCborAppendString(&ctx->cbor, name, nameLength);
|
||||||
|
eicCborAppendString(&ctx->cborEcdsa, name, nameLength);
|
||||||
ctx->accessCheckOk = true;
|
ctx->accessCheckOk = true;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -821,6 +872,7 @@ bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encr
|
|||||||
}
|
}
|
||||||
|
|
||||||
eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
|
eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
|
||||||
|
eicCborAppend(&ctx->cborEcdsa, content, encryptedContentSize - 28);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -842,6 +894,40 @@ bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMac
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
eicCborFinal(&ctx->cbor, digestToBeMaced);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ typedef struct {
|
|||||||
// aren't.
|
// aren't.
|
||||||
bool requestMessageValidated;
|
bool requestMessageValidated;
|
||||||
bool buildCbor;
|
bool buildCbor;
|
||||||
|
bool buildCborEcdsa;
|
||||||
|
|
||||||
// Set to true initialized as a test credential.
|
// Set to true initialized as a test credential.
|
||||||
bool testCredential;
|
bool testCredential;
|
||||||
@@ -101,6 +102,12 @@ typedef struct {
|
|||||||
|
|
||||||
size_t expectedCborSizeAtEnd;
|
size_t expectedCborSizeAtEnd;
|
||||||
EicCbor cbor;
|
EicCbor cbor;
|
||||||
|
|
||||||
|
// The selected DeviceKey / AuthKey
|
||||||
|
uint8_t deviceKeyPriv[EIC_P256_PRIV_KEY_SIZE];
|
||||||
|
|
||||||
|
EicCbor cborEcdsa;
|
||||||
|
size_t expectedCborEcdsaSizeAtEnd;
|
||||||
} EicPresentation;
|
} EicPresentation;
|
||||||
|
|
||||||
// If sessionId is zero (EIC_PRESENTATION_ID_UNSET), the presentation object is not associated
|
// 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,
|
EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
|
||||||
} EicAccessCheckResult;
|
} 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,
|
bool eicPresentationPrepareDeviceAuthentication(
|
||||||
size_t sessionTranscriptSize,
|
EicPresentation* ctx, const uint8_t* sessionTranscript, size_t sessionTranscriptSize,
|
||||||
const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
|
const uint8_t* readerEphemeralPublicKey, size_t readerEphemeralPublicKeySize,
|
||||||
const uint8_t signingKeyBlob[60], const char* docType,
|
const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
|
||||||
size_t docTypeLength, unsigned int numNamespacesWithValues,
|
unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize);
|
||||||
size_t expectedDeviceNamespacesSize);
|
|
||||||
|
|
||||||
// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
|
// 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
|
// 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,
|
bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
|
||||||
size_t* digestToBeMacedSize);
|
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 data returned in |signatureOfToBeSigned| contains the ECDSA signature of
|
||||||
// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
|
// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
|
||||||
// where content is set to the ProofOfDeletion CBOR.
|
// where content is set to the ProofOfDeletion CBOR.
|
||||||
|
|||||||
@@ -84,30 +84,35 @@ bool eicSessionGetAuthChallenge(EicSession* ctx, uint64_t* outAuthChallenge) {
|
|||||||
bool eicSessionGetEphemeralKeyPair(EicSession* ctx,
|
bool eicSessionGetEphemeralKeyPair(EicSession* ctx,
|
||||||
uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
|
uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
|
||||||
eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
|
eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
|
||||||
|
ctx->getEphemeralKeyPairCalled = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eicSessionSetReaderEphemeralPublicKey(
|
bool eicSessionSetReaderEphemeralPublicKey(
|
||||||
EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]) {
|
EicSession* ctx, const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE]) {
|
||||||
eicMemCpy(ctx->readerEphemeralPublicKey, readerEphemeralPublicKey, EIC_P256_PUB_KEY_SIZE);
|
eicMemCpy(ctx->readerEphemeralPublicKey, readerEphemeralPublicKey, EIC_P256_PUB_KEY_SIZE);
|
||||||
|
ctx->readerEphemeralPublicKeySize = EIC_P256_PUB_KEY_SIZE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript,
|
bool eicSessionSetSessionTranscript(EicSession* ctx, const uint8_t* sessionTranscript,
|
||||||
size_t sessionTranscriptSize) {
|
size_t sessionTranscriptSize) {
|
||||||
// Only accept the SessionTranscript if X and Y from the ephemeral key
|
// If mdoc session encryption is in use, only accept the
|
||||||
// we created is somewhere in SessionTranscript...
|
// SessionTranscript if X and Y from the ephemeral key we created
|
||||||
|
// is somewhere in SessionTranscript...
|
||||||
//
|
//
|
||||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize, ctx->ephemeralPublicKey,
|
if (ctx->getEphemeralKeyPairCalled) {
|
||||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
if (eicMemMem(sessionTranscript, sessionTranscriptSize, ctx->ephemeralPublicKey,
|
||||||
eicDebug("Error finding X from ephemeralPublicKey in sessionTranscript");
|
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||||
return false;
|
eicDebug("Error finding X from ephemeralPublicKey in sessionTranscript");
|
||||||
}
|
return false;
|
||||||
if (eicMemMem(sessionTranscript, sessionTranscriptSize,
|
}
|
||||||
ctx->ephemeralPublicKey + EIC_P256_PUB_KEY_SIZE / 2,
|
if (eicMemMem(sessionTranscript, sessionTranscriptSize,
|
||||||
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
ctx->ephemeralPublicKey + EIC_P256_PUB_KEY_SIZE / 2,
|
||||||
eicDebug("Error finding Y from ephemeralPublicKey in sessionTranscript");
|
EIC_P256_PUB_KEY_SIZE / 2) == NULL) {
|
||||||
return false;
|
eicDebug("Error finding Y from ephemeralPublicKey in sessionTranscript");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To save space we only store the SHA-256 of SessionTranscript
|
// 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
|
// A non-zero number unique for this EicSession instance
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
|
||||||
|
// Set to true iff eicSessionGetEphemeralKeyPair() has been called.
|
||||||
|
bool getEphemeralKeyPairCalled;
|
||||||
|
|
||||||
// The challenge generated at construction time by eicSessionInit().
|
// The challenge generated at construction time by eicSessionInit().
|
||||||
uint64_t authChallenge;
|
uint64_t authChallenge;
|
||||||
|
|
||||||
@@ -41,6 +44,7 @@ typedef struct {
|
|||||||
|
|
||||||
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
|
uint8_t sessionTranscriptSha256[EIC_SHA256_DIGEST_SIZE];
|
||||||
|
|
||||||
|
size_t readerEphemeralPublicKeySize;
|
||||||
} EicSession;
|
} EicSession;
|
||||||
|
|
||||||
bool eicSessionInit(EicSession* ctx);
|
bool eicSessionInit(EicSession* ctx);
|
||||||
|
|||||||
@@ -441,8 +441,18 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vector<uint8_t> mac;
|
vector<uint8_t> mac;
|
||||||
|
vector<uint8_t> ecdsaSignature;
|
||||||
vector<uint8_t> deviceNameSpacesEncoded;
|
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, {});
|
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||||
ASSERT_EQ(
|
ASSERT_EQ(
|
||||||
"{\n"
|
"{\n"
|
||||||
@@ -475,6 +485,21 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
ASSERT_TRUE(calculatedMac);
|
ASSERT_TRUE(calculatedMac);
|
||||||
EXPECT_EQ(mac, 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
|
// 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
|
// are envisioned to do - one call to get the data elements, another to get
|
||||||
// an empty DeviceSignedItems and corresponding MAC.
|
// an empty DeviceSignedItems and corresponding MAC.
|
||||||
@@ -486,7 +511,16 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||||
testEntriesEntryCounts)
|
testEntriesEntryCounts)
|
||||||
.isOk());
|
.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, {});
|
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||||
ASSERT_EQ("{}", cborPretty);
|
ASSERT_EQ("{}", cborPretty);
|
||||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||||
@@ -497,6 +531,21 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
ASSERT_TRUE(calculatedMac);
|
ASSERT_TRUE(calculatedMac);
|
||||||
EXPECT_EQ(mac, 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
|
// Some mDL apps might send a request but with a single empty
|
||||||
// namespace. Check that too.
|
// namespace. Check that too.
|
||||||
RequestNamespace emptyRequestNS;
|
RequestNamespace emptyRequestNS;
|
||||||
@@ -508,7 +557,16 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature,
|
||||||
testEntriesEntryCounts)
|
testEntriesEntryCounts)
|
||||||
.isOk());
|
.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, {});
|
cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
|
||||||
ASSERT_EQ("{}", cborPretty);
|
ASSERT_EQ("{}", cborPretty);
|
||||||
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
// Calculate DeviceAuthentication and MAC (MACing key hasn't changed)
|
||||||
@@ -518,6 +576,248 @@ TEST_P(EndToEndTests, createAndRetrieveCredential) {
|
|||||||
eMacKey.value()); // EMacKey
|
eMacKey.value()); // EMacKey
|
||||||
ASSERT_TRUE(calculatedMac);
|
ASSERT_TRUE(calculatedMac);
|
||||||
EXPECT_EQ(mac, 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);
|
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests);
|
||||||
|
|||||||
Reference in New Issue
Block a user