Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f90b7d83 authored by David Zeuthen's avatar David Zeuthen
Browse files

Identity: Update for changes to ISO 18013-5.

Key derivation for session encryption and MACing now involves mixing
in SessionTranscriptBytes. Update docs, default implementation, and
VTS tests to reflect this.

Also, the standard changed such that instead of DeviceAuthentication
being MACed or signed, it's instead DeviceAuthenticationBytes which is
defined as #6.24(bstr .cbor DeviceAuthentication). The same also for
ReaderAuthentication, now ReaderAuthenticationBytes is the CBOR which
is signed by the reader.

Also update the URL for CDDL since it's now a published RFC.

Bug: 159482543
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Merged-In: I73fc7eb48ffb71e00a8b54849266ed814295fa39
Change-Id: I4bb5040895cf24a7089b7e36e05eeaf26496381e
parent fbcb5a89
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -151,8 +151,8 @@ interface IIdentityCredential {
     *   IntentToRetain = bool
     *
     * For the readerSignature parameter, this can either be empty or if non-empty it
     * must be a COSE_Sign1 structure with an ECDSA signature over the content of the
     * CBOR conforming to the following CDDL:
     * must be a COSE_Sign1 where the payload is the bytes of the
     * ReaderAuthenticationBytes CBOR defined below:
     *
     *     ReaderAuthentication = [
     *       "ReaderAuthentication",
@@ -164,6 +164,8 @@ interface IIdentityCredential {
     *
     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
     *
     *     ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
     *
     * The public key corresponding to the key used to made signature, can be found in the
     * 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described
     * in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element
@@ -278,7 +280,7 @@ interface IIdentityCredential {
     *
     * @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 DeviceAuthentication as defined below.
     *    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)
@@ -299,6 +301,8 @@ interface IIdentityCredential {
     *
     *        DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
     *
     *        DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
     *
     *    where
     *
     *        DeviceNameSpaces = {
+1 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ import android.hardware.identity.CipherSuite;
 * Various fields need to be encoded as precisely-specified byte arrays.  Where existing standards
 * define appropriate encodings, those are used.  For example, X.509 certificates.  Where new
 * encodings are needed, CBOR is used.  CBOR maps are described in CDDL notation
 * (https://tools.ietf.org/html/draft-ietf-cbor-cddl-06).
 * (https://tools.ietf.org/html/rfc8610).
 *
 * All binder calls in the HAL may return a ServiceSpecificException with statuses from the
 * STATUS_* integers defined in this interface. Each method states which status can be returned
+24 −10
Original line number Diff line number Diff line
@@ -39,6 +39,10 @@ using ::std::optional;
using namespace ::android::hardware::identity;

int IdentityCredential::initialize() {
    if (credentialData_.size() == 0) {
        LOG(ERROR) << "CredentialData is empty";
        return IIdentityCredentialStore::STATUS_INVALID_DATA;
    }
    auto [item, _, message] = cppbor::parse(credentialData_);
    if (item == nullptr) {
        LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
@@ -316,13 +320,16 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval(
        }

        const vector<uint8_t>& itemsRequestBytes = itemsRequest;
        vector<uint8_t> dataThatWasSigned = cppbor::Array()
        vector<uint8_t> encodedReaderAuthentication =
                cppbor::Array()
                        .add("ReaderAuthentication")
                        .add(sessionTranscriptItem_->clone())
                        .add(cppbor::Semantic(24, itemsRequestBytes))
                        .encode();
        vector<uint8_t> encodedReaderAuthenticationBytes =
                cppbor::Semantic(24, encodedReaderAuthentication).encode();
        if (!support::coseCheckEcDsaSignature(readerSignature,
                                              dataThatWasSigned,  // detached content
                                              encodedReaderAuthenticationBytes,  // detached content
                                              readerPublicKey.value())) {
            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
@@ -779,7 +786,7 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
        array.add(sessionTranscriptItem_->clone());
        array.add(docType_);
        array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
        vector<uint8_t> encodedDeviceAuthentication = array.encode();
        vector<uint8_t> deviceAuthenticationBytes = cppbor::Semantic(24, array.encode()).encode();

        vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
        optional<vector<uint8_t>> signingKey =
@@ -797,9 +804,16 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
                    IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
        }

        // Mix-in SessionTranscriptBytes
        vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
        vector<uint8_t> sharedSecretWithSessionTranscriptBytes = sharedSecret.value();
        std::copy(sessionTranscriptBytes.begin(), sessionTranscriptBytes.end(),
                  std::back_inserter(sharedSecretWithSessionTranscriptBytes));

        vector<uint8_t> salt = {0x00};
        vector<uint8_t> info = {};
        optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
        optional<vector<uint8_t>> derivedKey =
                support::hkdf(sharedSecretWithSessionTranscriptBytes, salt, info, 32);
        if (!derivedKey) {
            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                    IIdentityCredentialStore::STATUS_FAILED,
@@ -807,7 +821,7 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<int8_t>* outMac,
        }

        mac = support::coseMac0(derivedKey.value(), {},      // payload
                                encodedDeviceAuthentication);  // additionalData
                                deviceAuthenticationBytes);  // detached content
        if (!mac) {
            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                    IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
+22 −16
Original line number Diff line number Diff line
@@ -289,16 +289,19 @@ void ReaderAuthTests::retrieveData(const vector<uint8_t>& readerPrivateKey,
                                                            .add("Accessible by None", false)))
                        .encode();
    }
    vector<uint8_t> dataToSign = cppbor::Array()
    vector<uint8_t> encodedReaderAuthentication =
            cppbor::Array()
                    .add("ReaderAuthentication")
                    .add(sessionTranscript.clone())
                    .add(cppbor::Semantic(24, itemsRequestBytes))
                    .encode();
    vector<uint8_t> encodedReaderAuthenticationBytes =
            cppbor::Semantic(24, encodedReaderAuthentication).encode();

    optional<vector<uint8_t>> readerSignature =
            support::coseSignEcDsa(readerPrivateKey,                  // private key for reader
                                   {},                                // content
                                   dataToSign,        // detached content
                                   encodedReaderAuthenticationBytes,  // detached content
                                   support::certificateChainJoin(readerCertChain));
    ASSERT_TRUE(readerSignature);

@@ -528,17 +531,20 @@ TEST_P(ReaderAuthTests, ephemeralKeyNotInSessionTranscript) {
                                                        .add("Accessible by C", false)
                                                        .add("Accessible by None", false)))
                    .encode();
    vector<uint8_t> dataToSign = cppbor::Array()
    vector<uint8_t> encodedReaderAuthentication =
            cppbor::Array()
                    .add("ReaderAuthentication")
                    .add(sessionTranscript.clone())
                    .add(cppbor::Semantic(24, itemsRequestBytes))
                    .encode();
    vector<uint8_t> encodedReaderAuthenticationBytes =
            cppbor::Semantic(24, encodedReaderAuthentication).encode();

    vector<vector<uint8_t>> readerCertChain = {cert_reader_SelfSigned_};
    optional<vector<uint8_t>> readerSignature =
            support::coseSignEcDsa(readerPrivateKey_,                 // private key for reader
                                   {},                                // content
                                   dataToSign,         // detached content
                                   encodedReaderAuthenticationBytes,  // detached content
                                   support::certificateChainJoin(readerCertChain));
    ASSERT_TRUE(readerSignature);

+25 −14
Original line number Diff line number Diff line
@@ -319,7 +319,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
    cppbor::Array sessionTranscript = cppbor::Array()
                                              .add(cppbor::Semantic(24, deviceEngagementBytes))
                                              .add(cppbor::Semantic(24, eReaderPubBytes));
    vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
    vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode();

    vector<uint8_t> itemsRequestBytes =
            cppbor::Map("nameSpaces",
@@ -347,14 +347,17 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
            "  },\n"
            "}",
            cborPretty);
    vector<uint8_t> dataToSign = cppbor::Array()
    vector<uint8_t> encodedReaderAuthentication =
            cppbor::Array()
                    .add("ReaderAuthentication")
                    .add(sessionTranscript.clone())
                    .add(cppbor::Semantic(24, itemsRequestBytes))
                    .encode();
    vector<uint8_t> encodedReaderAuthenticationBytes =
            cppbor::Semantic(24, encodedReaderAuthentication).encode();
    optional<vector<uint8_t>> readerSignature =
            support::coseSignEcDsa(readerKey, {},                     // content
                                   dataToSign,     // detached content
                                   encodedReaderAuthenticationBytes,  // detached content
                                   readerCertificate.value());
    ASSERT_TRUE(readerSignature);

@@ -388,7 +391,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
    credential->setVerificationToken(verificationToken);
    ASSERT_TRUE(credential
                        ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes,
                                         signingKeyBlob, sessionTranscriptBytes,
                                         signingKeyBlob, sessionTranscriptEncoded,
                                         readerSignature.value(), testEntriesEntryCounts)
                        .isOk());

@@ -432,7 +435,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
            "  },\n"
            "}",
            cborPretty);
    // The data that is MACed is ["DeviceAuthentication", sessionTranscriptBytes, docType,
    // The data that is MACed is ["DeviceAuthentication", sessionTranscript, docType,
    // deviceNameSpacesBytes] so build up that structure
    cppbor::Array deviceAuthentication;
    deviceAuthentication.add("DeviceAuthentication");
@@ -441,7 +444,8 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
    string docType = "org.iso.18013-5.2019.mdl";
    deviceAuthentication.add(docType);
    deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
    vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
    vector<uint8_t> deviceAuthenticationBytes =
            cppbor::Semantic(24, deviceAuthentication.encode()).encode();

    // Derive the key used for MACing.
    optional<vector<uint8_t>> readerEphemeralPrivateKey =
@@ -449,13 +453,20 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
    optional<vector<uint8_t>> sharedSecret =
            support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value());
    ASSERT_TRUE(sharedSecret);
    // Mix-in SessionTranscriptBytes
    vector<uint8_t> sessionTranscriptBytes =
            cppbor::Semantic(24, sessionTranscript.encode()).encode();
    vector<uint8_t> sharedSecretWithSessionTranscriptBytes = sharedSecret.value();
    std::copy(sessionTranscriptBytes.begin(), sessionTranscriptBytes.end(),
              std::back_inserter(sharedSecretWithSessionTranscriptBytes));
    vector<uint8_t> salt = {0x00};
    vector<uint8_t> info = {};
    optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
    optional<vector<uint8_t>> derivedKey =
            support::hkdf(sharedSecretWithSessionTranscriptBytes, salt, info, 32);
    ASSERT_TRUE(derivedKey);
    optional<vector<uint8_t>> calculatedMac =
            support::coseMac0(derivedKey.value(), {},      // payload
                              encodedDeviceAuthentication);  // detached content
                              deviceAuthenticationBytes);  // detached content
    ASSERT_TRUE(calculatedMac);
    EXPECT_EQ(mac, calculatedMac);
}