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

Commit 7d2350be authored by Jing-yan, Jang's avatar Jing-yan, Jang Committed by Automerger Merge Worker
Browse files

Merge "Identity Credential: Add some support functions for mDL oem Hal." into...

Merge "Identity Credential: Add some support functions for mDL oem Hal." into rvc-dev am: 4200c3b5

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

Change-Id: I73eb4e68d290967a612e843e831d99b8e9b4aa28
parents edaaae7a 4200c3b5
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ using ::std::optional;
using ::std::string;
using ::std::tuple;
using ::std::vector;
using ::std::pair;

// ---------------------------------------------------------------------------
// Miscellaneous utilities.
@@ -119,6 +120,12 @@ optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vec
optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAndAttestation(
        const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId);

// Like createEcKeyPairAndAttestation() but allows you to choose the public key.
//
optional<vector<vector<uint8_t>>> createAttestationForEcPublicKey(
        const vector<uint8_t>& publicKey, const vector<uint8_t>& challenge,
        const vector<uint8_t>& applicationId);

// Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the
// PKCS#8 encoded key-pair.
//
@@ -155,6 +162,12 @@ optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, con
//
optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data);

// Like signEcDsa() but instead of taking the data to be signed, takes a digest
// of it instead.
//
optional<vector<uint8_t>> signEcDsaDigest(const vector<uint8_t>& key,
                                          const vector<uint8_t>& dataDigest);

// Calculates the HMAC with SHA-256 for |data| using |key|. The calculated HMAC
// is returned and will be 32 bytes.
//
@@ -175,6 +188,27 @@ bool checkEcDsaSignature(const vector<uint8_t>& digest, const vector<uint8_t>& s
//
optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& certificateChain);

// Extracts the public-key from the top-most certificate in |certificateChain|
// (which should be a concatenated chain of DER-encoded X.509 certificates).
//
// Return offset and size of the public-key
//
optional<pair<size_t, size_t>> certificateFindPublicKey(const vector<uint8_t>& x509Certificate);

// Extracts the TbsCertificate from the top-most certificate in |certificateChain|
// (which should be a concatenated chain of DER-encoded X.509 certificates).
//
// Return offset and size of the TbsCertificate
//
optional<pair<size_t, size_t>> certificateTbsCertificate(const vector<uint8_t>& x509Certificate);

// Extracts the Signature from the top-most certificate in |certificateChain|
// (which should be a concatenated chain of DER-encoded X.509 certificates).
//
// Return offset and size of the Signature
//
optional<pair<size_t, size_t>> certificateFindSignature(const vector<uint8_t>& x509Certificate);

// Generates a X.509 certificate for |publicKey| (which must be in the format
// returned by ecKeyPairGetPublicKey()).
//
@@ -231,6 +265,11 @@ optional<vector<vector<uint8_t>>> certificateChainSplit(const vector<uint8_t>& c
//
bool certificateChainValidate(const vector<uint8_t>& certificateChain);

// Returns true if |certificate| is signed by |publicKey|.
//
bool certificateSignedByPublicKey(const vector<uint8_t>& certificate,
                                  const vector<uint8_t>& publicKey);

// Signs |data| and |detachedContent| with |key| (which must be in the format
// returned by ecKeyPairGetPrivateKey()).
//
@@ -243,6 +282,21 @@ optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector
                                        const vector<uint8_t>& detachedContent,
                                        const vector<uint8_t>& certificateChain);

// Creates a COSE_Signature1 where |signatureToBeSigned| is the ECDSA signature
// of the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process".
//
// The |signatureToBeSigned| is expected to be 64 bytes and contain the R value,
// then the S value.
//
// The |data| parameter will be included in the COSE_Sign1 CBOR.
//
// If |certificateChain| is non-empty it's included in the 'x5chain'
// protected header element (as as described in'draft-ietf-cose-x509-04').
//
optional<vector<uint8_t>> coseSignEcDsaWithSignature(const vector<uint8_t>& signatureToBeSigned,
                                                     const vector<uint8_t>& data,
                                                     const vector<uint8_t>& certificateChain);

// Checks that |signatureCoseSign1| (in COSE_Sign1 format) is a valid signature
// made with |public_key| (which must be in the format returned by
// ecKeyPairGetPublicKey()) where |detachedContent| is the detached content.
@@ -251,9 +305,23 @@ bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
                             const vector<uint8_t>& detachedContent,
                             const vector<uint8_t>& publicKey);

// Converts a DER-encoded signature to the format used in 'signature' bstr in COSE_Sign1.
bool ecdsaSignatureDerToCose(const vector<uint8_t>& ecdsaDerSignature,
                             vector<uint8_t>& ecdsaCoseSignature);

// Converts from the format in in 'signature' bstr in COSE_Sign1 to DER encoding.
bool ecdsaSignatureCoseToDer(const vector<uint8_t>& ecdsaCoseSignature,
                             vector<uint8_t>& ecdsaDerSignature);

// Extracts the payload from a COSE_Sign1.
optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1);

// Extracts the signature (of the ToBeSigned CBOR) from a COSE_Sign1.
optional<vector<uint8_t>> coseSignGetSignature(const vector<uint8_t>& signatureCoseSign1);

// Extracts the signature algorithm from a COSE_Sign1.
optional<int> coseSignGetAlg(const vector<uint8_t>& signatureCoseSign1);

// Extracts the X.509 certificate chain, if present. Returns the data as a
// concatenated chain of DER-encoded X.509 certificates
//
@@ -269,6 +337,16 @@ optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCos
optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint8_t>& data,
                                   const vector<uint8_t>& detachedContent);

// Creates a COSE_Mac0 where |digestToBeMaced| is the HMAC-SHA256
// of the ToBeMaced CBOR from RFC 8051 "6.3. How to Compute and Verify a MAC".
//
// The |digestToBeMaced| is expected to be 32 bytes.
//
// The |data| parameter will be included in the COSE_Mac0 CBOR.
//
optional<vector<uint8_t>> coseMacWithDigest(const vector<uint8_t>& digestToBeMaced,
                                            const vector<uint8_t>& data);

// ---------------------------------------------------------------------------
// Utility functions specific to IdentityCredential.
// ---------------------------------------------------------------------------
+362 −3
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <chrono>
#include <iomanip>

#include <openssl/aes.h>
@@ -684,6 +685,48 @@ static bool parseX509Certificates(const vector<uint8_t>& certificateChain,
    return true;
}

bool certificateSignedByPublicKey(const vector<uint8_t>& certificate,
                                  const vector<uint8_t>& publicKey) {
    const unsigned char* p = certificate.data();
    auto x509 = X509_Ptr(d2i_X509(nullptr, &p, certificate.size()));
    if (x509 == nullptr) {
        LOG(ERROR) << "Error parsing X509 certificate";
        return false;
    }

    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
    auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
    if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
        1) {
        LOG(ERROR) << "Error decoding publicKey";
        return false;
    }
    auto ecKey = EC_KEY_Ptr(EC_KEY_new());
    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
    if (ecKey.get() == nullptr || pkey.get() == nullptr) {
        LOG(ERROR) << "Memory allocation failed";
        return false;
    }
    if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
        LOG(ERROR) << "Error setting group";
        return false;
    }
    if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
        LOG(ERROR) << "Error setting point";
        return false;
    }
    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
        LOG(ERROR) << "Error setting key";
        return false;
    }

    if (X509_verify(x509.get(), pkey.get()) != 1) {
        return false;
    }

    return true;
}

// TODO: Right now the only check we perform is to check that each certificate
//       is signed by its successor. We should - but currently don't - also check
//       things like valid dates etc.
@@ -770,7 +813,8 @@ vector<uint8_t> sha256(const vector<uint8_t>& data) {
    return ret;
}

optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data) {
optional<vector<uint8_t>> signEcDsaDigest(const vector<uint8_t>& key,
                                          const vector<uint8_t>& dataDigest) {
    auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr));
    if (bn.get() == nullptr) {
        LOG(ERROR) << "Error creating BIGNUM";
@@ -783,8 +827,7 @@ optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uin
        return {};
    }

    auto digest = sha256(data);
    ECDSA_SIG* sig = ECDSA_do_sign(digest.data(), digest.size(), ec_key.get());
    ECDSA_SIG* sig = ECDSA_do_sign(dataDigest.data(), dataDigest.size(), ec_key.get());
    if (sig == nullptr) {
        LOG(ERROR) << "Error signing digest";
        return {};
@@ -798,6 +841,10 @@ optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uin
    return signature;
}

optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data) {
    return signEcDsaDigest(key, sha256(data));
}

optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<uint8_t>& data) {
    HMAC_CTX ctx;
    HMAC_CTX_init(&ctx);
@@ -955,6 +1002,51 @@ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAnd
    return make_pair(keyPair, attestationCert.value());
}

optional<vector<vector<uint8_t>>> createAttestationForEcPublicKey(
        const vector<uint8_t>& publicKey, const vector<uint8_t>& challenge,
        const vector<uint8_t>& applicationId) {
    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
    auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
    if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
        1) {
        LOG(ERROR) << "Error decoding publicKey";
        return {};
    }
    auto ecKey = EC_KEY_Ptr(EC_KEY_new());
    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
    if (ecKey.get() == nullptr || pkey.get() == nullptr) {
        LOG(ERROR) << "Memory allocation failed";
        return {};
    }
    if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
        LOG(ERROR) << "Error setting group";
        return {};
    }
    if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
        LOG(ERROR) << "Error setting point";
        return {};
    }
    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
        LOG(ERROR) << "Error setting key";
        return {};
    }

    uint64_t now = (std::chrono::duration_cast<std::chrono::nanoseconds>(
                    std::chrono::system_clock::now().time_since_epoch()).
                    count()/ 1000000000);
    uint64_t secondsInOneYear = 365 * 24 * 60 * 60;
    uint64_t expireTimeMs = (now + secondsInOneYear) * 1000;

    optional<vector<vector<uint8_t>>> attestationCert =
            createAttestation(pkey.get(), applicationId, challenge, now * 1000, expireTimeMs);
    if (!attestationCert) {
        LOG(ERROR) << "Error create attestation from key and challenge";
        return {};
    }

    return attestationCert.value();
}

optional<vector<uint8_t>> createEcKeyPair() {
    auto ec_key = EC_KEY_Ptr(EC_KEY_new());
    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
@@ -1477,6 +1569,120 @@ optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& c
    return publicKey;
}

optional<pair<size_t, size_t>> certificateFindPublicKey(const vector<uint8_t>& x509Certificate) {
    vector<X509_Ptr> certs;
    if (!parseX509Certificates(x509Certificate, certs)) {
        return {};
    }
    if (certs.size() < 1) {
        LOG(ERROR) << "No certificates in chain";
        return {};
    }

    auto pkey = EVP_PKEY_Ptr(X509_get_pubkey(certs[0].get()));
    if (pkey.get() == nullptr) {
        LOG(ERROR) << "No public key";
        return {};
    }

    auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
    if (ecKey.get() == nullptr) {
        LOG(ERROR) << "Failed getting EC key";
        return {};
    }

    auto ecGroup = EC_KEY_get0_group(ecKey.get());
    auto ecPoint = EC_KEY_get0_public_key(ecKey.get());
    int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0,
                                  nullptr);
    if (size == 0) {
        LOG(ERROR) << "Error generating public key encoding";
        return {};
    }
    vector<uint8_t> publicKey;
    publicKey.resize(size);
    EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(),
                       publicKey.size(), nullptr);

    size_t publicKeyOffset = 0;
    size_t publicKeySize = (size_t)size;
    void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(),
                            (const void*)publicKey.data(), publicKey.size());

    if (location == NULL) {
        LOG(ERROR) << "Error finding publicKey from x509Certificate";
        return {};
    }
    publicKeyOffset = (size_t)((const char*)location - (const char*)x509Certificate.data());

    return std::make_pair(publicKeyOffset, publicKeySize);
}

optional<pair<size_t, size_t>> certificateTbsCertificate(const vector<uint8_t>& x509Certificate) {
    vector<X509_Ptr> certs;
    if (!parseX509Certificates(x509Certificate, certs)) {
        return {};
    }
    if (certs.size() < 1) {
        LOG(ERROR) << "No certificates in chain";
        return {};
    }

    unsigned char* buf = NULL;
    int len = i2d_re_X509_tbs(certs[0].get(), &buf);
    if ((len < 0) || (buf == NULL)) {
        LOG(ERROR) << "fail to extract tbsCertificate in x509Certificate";
        return {};
    }

    vector<uint8_t> tbsCertificate(len);
    memcpy(tbsCertificate.data(), buf, len);

    size_t tbsCertificateOffset = 0;
    size_t tbsCertificateSize = (size_t)len;
    void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(),
                            (const void*)tbsCertificate.data(), tbsCertificate.size());

    if (location == NULL) {
        LOG(ERROR) << "Error finding tbsCertificate from x509Certificate";
        return {};
    }
    tbsCertificateOffset = (size_t)((const char*)location - (const char*)x509Certificate.data());

    return std::make_pair(tbsCertificateOffset, tbsCertificateSize);
}

optional<pair<size_t, size_t>> certificateFindSignature(const vector<uint8_t>& x509Certificate) {
    vector<X509_Ptr> certs;
    if (!parseX509Certificates(x509Certificate, certs)) {
        return {};
    }
    if (certs.size() < 1) {
        LOG(ERROR) << "No certificates in chain";
        return {};
    }

    ASN1_BIT_STRING* psig;
    X509_ALGOR* palg;
    X509_get0_signature((const ASN1_BIT_STRING**)&psig, (const X509_ALGOR**)&palg, certs[0].get());

    vector<char> signature(psig->length);
    memcpy(signature.data(), psig->data, psig->length);

    size_t signatureOffset = 0;
    size_t signatureSize = (size_t)psig->length;
    void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(),
                            (const void*)signature.data(), signature.size());

    if (location == NULL) {
        LOG(ERROR) << "Error finding signature from x509Certificate";
        return {};
    }
    signatureOffset = (size_t)((const char*)location - (const char*)x509Certificate.data());

    return std::make_pair(signatureOffset, signatureSize);
}

// ---------------------------------------------------------------------------
// COSE Utility Functions
// ---------------------------------------------------------------------------
@@ -1574,6 +1780,55 @@ bool ecdsaSignatureDerToCose(const vector<uint8_t>& ecdsaDerSignature,
    return true;
}

optional<vector<uint8_t>> coseSignEcDsaWithSignature(const vector<uint8_t>& signatureToBeSigned,
                                                     const vector<uint8_t>& data,
                                                     const vector<uint8_t>& certificateChain) {
    if (signatureToBeSigned.size() != 64) {
        LOG(ERROR) << "Invalid size for signatureToBeSigned, expected 64 got "
                   << signatureToBeSigned.size();
        return {};
    }

    cppbor::Map unprotectedHeaders;
    cppbor::Map protectedHeaders;

    protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);

    if (certificateChain.size() != 0) {
        optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certificateChain);
        if (!certs) {
            LOG(ERROR) << "Error splitting certificate chain";
            return {};
        }
        if (certs.value().size() == 1) {
            unprotectedHeaders.add(COSE_LABEL_X5CHAIN, certs.value()[0]);
        } else {
            cppbor::Array certArray;
            for (const vector<uint8_t>& cert : certs.value()) {
                certArray.add(cert);
            }
            unprotectedHeaders.add(COSE_LABEL_X5CHAIN, std::move(certArray));
        }
    }

    vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);

    cppbor::Array coseSign1;
    coseSign1.add(encodedProtectedHeaders);
    coseSign1.add(std::move(unprotectedHeaders));
    if (data.size() == 0) {
        cppbor::Null nullValue;
        coseSign1.add(std::move(nullValue));
    } else {
        coseSign1.add(data);
    }
    coseSign1.add(signatureToBeSigned);
    vector<uint8_t> signatureCoseSign1;
    signatureCoseSign1 = coseSign1.encode();

    return signatureCoseSign1;
}

optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data,
                                        const vector<uint8_t>& detachedContent,
                                        const vector<uint8_t>& certificateChain) {
@@ -1709,6 +1964,35 @@ bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
    return true;
}

// Extracts the signature (of the ToBeSigned CBOR) from a COSE_Sign1.
optional<vector<uint8_t>> coseSignGetSignature(const vector<uint8_t>& signatureCoseSign1) {
    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
    if (item == nullptr) {
        LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
        return {};
    }
    const cppbor::Array* array = item->asArray();
    if (array == nullptr) {
        LOG(ERROR) << "Value for COSE_Sign1 is not an array";
        return {};
    }
    if (array->size() != 4) {
        LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
        return {};
    }

    vector<uint8_t> signature;
    const cppbor::Bstr* signatureAsBstr = (*array)[3]->asBstr();
    if (signatureAsBstr == nullptr) {
        LOG(ERROR) << "Value for signature is not a bstr";
        return {};
    }
    // Copy payload into |data|
    signature = signatureAsBstr->value();

    return signature;
}

optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1) {
    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
    if (item == nullptr) {
@@ -1746,6 +2030,59 @@ optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCos
    return data;
}

optional<int> coseSignGetAlg(const vector<uint8_t>& signatureCoseSign1) {
    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
    if (item == nullptr) {
        LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
        return {};
    }
    const cppbor::Array* array = item->asArray();
    if (array == nullptr) {
        LOG(ERROR) << "Value for COSE_Sign1 is not an array";
        return {};
    }
    if (array->size() != 4) {
        LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
        return {};
    }

    const cppbor::Bstr* protectedHeadersBytes = (*array)[0]->asBstr();
    if (protectedHeadersBytes == nullptr) {
        LOG(ERROR) << "Value for protectedHeaders is not a bstr";
        return {};
    }
    auto [item2, _2, message2] = cppbor::parse(protectedHeadersBytes->value());
    if (item2 == nullptr) {
        LOG(ERROR) << "Error parsing protectedHeaders: " << message2;
        return {};
    }
    const cppbor::Map* protectedHeaders = item2->asMap();
    if (protectedHeaders == nullptr) {
        LOG(ERROR) << "Decoded CBOR for protectedHeaders is not a map";
        return {};
    }

    for (size_t n = 0; n < protectedHeaders->size(); n++) {
        auto [keyItem, valueItem] = (*protectedHeaders)[n];
        const cppbor::Int* number = keyItem->asInt();
        if (number == nullptr) {
            LOG(ERROR) << "Key item in top-level map is not a number";
            return {};
        }
        int label = number->value();
        if (label == COSE_LABEL_ALG) {
            const cppbor::Int* number = valueItem->asInt();
            if (number != nullptr) {
                return number->value();
            }
            LOG(ERROR) << "Value for COSE_LABEL_ALG label is not a number";
            return {};
        }
    }
    LOG(ERROR) << "Did not find COSE_LABEL_ALG label in protected headers";
    return {};
}

optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCoseSign1) {
    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
    if (item == nullptr) {
@@ -1861,6 +2198,28 @@ optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint
    return array.encode();
}

optional<vector<uint8_t>> coseMacWithDigest(const vector<uint8_t>& digestToBeMaced,
                                            const vector<uint8_t>& data) {
    cppbor::Map unprotectedHeaders;
    cppbor::Map protectedHeaders;

    protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);

    vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);

    cppbor::Array array;
    array.add(encodedProtectedHeaders);
    array.add(std::move(unprotectedHeaders));
    if (data.size() == 0) {
        cppbor::Null nullValue;
        array.add(std::move(nullValue));
    } else {
        array.add(data);
    }
    array.add(digestToBeMaced);
    return array.encode();
}

// ---------------------------------------------------------------------------
// Utility functions specific to IdentityCredential.
// ---------------------------------------------------------------------------