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

Commit c8400772 authored by David Drysdale's avatar David Drysdale
Browse files

Commonize MacedPublicKey and ProtectedData checks

Test: VtsHalRemotelyProvisionedComponentTargetTest
Change-Id: I54dcaa6175b243219cb333f82278ecce08f8bb17
parent a5ddfbdc
Loading
Loading
Loading
Loading
+149 −209
Original line number Diff line number Diff line
@@ -52,6 +52,82 @@ bytevec string_to_bytevec(const char* s) {
    return bytevec(p, p + strlen(s));
}

void check_cose_key(const vector<uint8_t>& data, bool testMode) {
    auto [parsedPayload, __, payloadParseErr] = cppbor::parse(data);
    ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;

    // The following check assumes that canonical CBOR encoding is used for the COSE_Key.
    if (testMode) {
        EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
                    MatchesRegex("{\n"
                                 "  1 : 2,\n"   // kty: EC2
                                 "  3 : -7,\n"  // alg: ES256
                                 "  -1 : 1,\n"  // EC id: P256
                                 // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a
                                 // sequence of 32 hexadecimal bytes, enclosed in braces and
                                 // separated by commas. In this case, some Ed25519 public key.
                                 "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"  // pub_x: data
                                 "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"  // pub_y: data
                                 "  -70000 : null,\n"                              // test marker
                                 "}"));
    } else {
        EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
                    MatchesRegex("{\n"
                                 "  1 : 2,\n"   // kty: EC2
                                 "  3 : -7,\n"  // alg: ES256
                                 "  -1 : 1,\n"  // EC id: P256
                                 // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a
                                 // sequence of 32 hexadecimal bytes, enclosed in braces and
                                 // separated by commas. In this case, some Ed25519 public key.
                                 "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"  // pub_x: data
                                 "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"  // pub_y: data
                                 "}"));
    }
}

void check_maced_pubkey(const MacedPublicKey& macedPubKey, bool testMode,
                        vector<uint8_t>* payload_value) {
    auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
    ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;

    ASSERT_NE(coseMac0->asArray(), nullptr);
    ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);

    auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
    ASSERT_NE(protParms, nullptr);

    // Header label:value of 'alg': HMAC-256
    ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");

    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
    ASSERT_NE(unprotParms, nullptr);
    ASSERT_EQ(unprotParms->size(), 0);

    // The payload is a bstr holding an encoded COSE_Key
    auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
    ASSERT_NE(payload, nullptr);
    check_cose_key(payload->value(), testMode);

    auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
    ASSERT_TRUE(coseMac0Tag);
    auto extractedTag = coseMac0Tag->value();
    EXPECT_EQ(extractedTag.size(), 32U);

    // Compare with tag generated with kTestMacKey.  Should only match in test mode
    auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
                                                payload->value());
    ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();

    if (testMode) {
        EXPECT_EQ(*testTag, extractedTag);
    } else {
        EXPECT_NE(*testTag, extractedTag);
    }
    if (payload_value != nullptr) {
        *payload_value = payload->value();
    }
}

}  // namespace

class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> {
@@ -87,47 +163,7 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) {
    auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
    ASSERT_TRUE(status.isOk());

    auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
    ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;

    ASSERT_NE(coseMac0->asArray(), nullptr);
    ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);

    auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
    ASSERT_NE(protParms, nullptr);
    ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");

    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
    ASSERT_NE(unprotParms, nullptr);
    ASSERT_EQ(unprotParms->size(), 0);

    auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
    ASSERT_NE(payload, nullptr);
    auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
    ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
    EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
                MatchesRegex("{\n"
                             "  1 : 2,\n"
                             "  3 : -7,\n"
                             "  -1 : 1,\n"
                             // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
                             // 32 hexadecimal bytes, enclosed in braces and separated by commas.
                             // In this case, some Ed25519 public key.
                             "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
                             "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
                             "}"));

    auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
    ASSERT_TRUE(coseMac0Tag);
    auto extractedTag = coseMac0Tag->value();
    EXPECT_EQ(extractedTag.size(), 32U);

    // Compare with tag generated with kTestMacKey.  Shouldn't match.
    auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
                                                payload->value());
    ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();

    EXPECT_NE(*testTag, extractedTag);
    check_maced_pubkey(macedPubKey, testMode, nullptr);
}

/**
@@ -140,53 +176,12 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) {
    auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
    ASSERT_TRUE(status.isOk());

    auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
    ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;

    ASSERT_NE(coseMac0->asArray(), nullptr);
    ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);

    auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
    ASSERT_NE(protParms, nullptr);
    ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");

    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
    ASSERT_NE(unprotParms, nullptr);
    ASSERT_EQ(unprotParms->size(), 0);

    auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
    ASSERT_NE(payload, nullptr);
    auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
    ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
    EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
                MatchesRegex("{\n"
                             "  1 : 2,\n"
                             "  3 : -7,\n"
                             "  -1 : 1,\n"
                             // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
                             // 32 hexadecimal bytes, enclosed in braces and separated by commas.
                             // In this case, some Ed25519 public key.
                             "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
                             "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
                             "  -70000 : null,\n"
                             "}"));

    auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
    ASSERT_TRUE(coseMac0);
    auto extractedTag = coseMac0Tag->value();
    EXPECT_EQ(extractedTag.size(), 32U);

    // Compare with tag generated with kTestMacKey.  Should match.
    auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
                                                payload->value());
    ASSERT_TRUE(testTag) << testTag.message();

    EXPECT_EQ(*testTag, extractedTag);
    check_maced_pubkey(macedPubKey, testMode, nullptr);
}

class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
  protected:
    CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) {
    CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(32)) {
        auto chain = generateEekChain(3, eekId_);
        EXPECT_TRUE(chain) << chain.message();
        if (chain) eekChain_ = chain.moveValue();
@@ -201,40 +196,14 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
            auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob);
            ASSERT_TRUE(status.isOk()) << status.getMessage();

            auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey);
            ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr;
            ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?";
            ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount);

            auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload);
            ASSERT_TRUE(payload);
            ASSERT_TRUE(payload->asBstr());

            cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value()));
            vector<uint8_t> payload_value;
            check_maced_pubkey(key, testMode, &payload_value);
            cborKeysToSign_.add(cppbor::EncodedItem(payload_value));
        }
    }

    bytevec eekId_;
    EekChain eekChain_;
    std::vector<MacedPublicKey> keysToSign_;
    cppbor::Array cborKeysToSign_;
};

/**
 * Generate an empty certificate request in test mode, and decrypt and verify the structure and
 * content.
 */
TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
    bool testMode = true;
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto challenge = randomBytes(32);
    auto status = provisionable_->generateCertificateRequest(
            testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData,
            &keysToSignMac);
    ASSERT_TRUE(status.isOk()) << status.getMessage();

    void checkProtectedData(bool testMode, const cppbor::Array& keysToSign,
                            const bytevec& keysToSignMac, const ProtectedData& protectedData) {
        auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
        ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
        ASSERT_TRUE(parsedProtectedData->asArray());
@@ -270,7 +239,7 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
        auto& signingKey = bccContents->back().pubKey;
        auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
                                              cppbor::Array()  // DeviceInfo
                                                  .add(challenge)  //
                                                      .add(challenge_)
                                                      .add(cppbor::Map())
                                                      .encode());
        ASSERT_TRUE(macKey) << macKey.message();
@@ -281,13 +250,37 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
                                             .canonicalize()
                                             .encode())
                                .add(cppbor::Map())        // unprotected
                            .add(cppbor::Array().encode())   // payload (keysToSign)
                            .add(std::move(keysToSignMac));  // tag
                                .add(keysToSign.encode())  // payload (keysToSign)
                                .add(keysToSignMac);       // tag

        auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
        ASSERT_TRUE(macPayload) << macPayload.message();
    }

    bytevec eekId_;
    EekChain eekChain_;
    bytevec challenge_;
    std::vector<MacedPublicKey> keysToSign_;
    cppbor::Array cborKeysToSign_;
};

/**
 * Generate an empty certificate request in test mode, and decrypt and verify the structure and
 * content.
 */
TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
    bool testMode = true;
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto status = provisionable_->generateCertificateRequest(
            testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
            &keysToSignMac);
    ASSERT_TRUE(status.isOk()) << status.getMessage();

    checkProtectedData(testMode, cppbor::Array(), keysToSignMac, protectedData);
}

/**
 * Generate an empty certificate request in prod mode.  Generation will fail because we don't have a
 * valid GEEK.
@@ -300,9 +293,8 @@ TEST_P(CertificateRequestTest, EmptyRequest_prodMode) {
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto challenge = randomBytes(32);
    auto status = provisionable_->generateCertificateRequest(
            testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData,
            testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
            &keysToSignMac);
    ASSERT_FALSE(status.isOk());
    ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
@@ -318,62 +310,12 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) {
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto challenge = randomBytes(32);
    auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain,
                                                             challenge, &deviceInfo, &protectedData,
                                                             &keysToSignMac);
                                                             challenge_, &deviceInfo,
                                                             &protectedData, &keysToSignMac);
    ASSERT_TRUE(status.isOk()) << status.getMessage();

    auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
    ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
    ASSERT_TRUE(parsedProtectedData->asArray());
    ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);

    auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
    ASSERT_TRUE(senderPubkey) << senderPubkey.message();
    EXPECT_EQ(senderPubkey->second, eekId_);

    auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
                                            senderPubkey->first, false /* senderIsA */);
    ASSERT_TRUE(sessionKey) << sessionKey.message();

    auto protectedDataPayload =
            decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
    ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();

    auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
    ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
    ASSERT_TRUE(parsedPayload->asArray());
    EXPECT_EQ(parsedPayload->asArray()->size(), 2U);

    auto& signedMac = parsedPayload->asArray()->get(0);
    auto& bcc = parsedPayload->asArray()->get(1);
    ASSERT_TRUE(signedMac && signedMac->asArray());
    ASSERT_TRUE(bcc);

    auto bccContents = validateBcc(bcc->asArray());
    ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get());
    ASSERT_GT(bccContents->size(), 0U);

    auto& signingKey = bccContents->back().pubKey;
    auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
                                          cppbor::Array()          // DeviceInfo
                                                  .add(challenge)  //
                                                  .add(cppbor::Array())
                                                  .encode());
    ASSERT_TRUE(macKey) << macKey.message();

    auto coseMac0 = cppbor::Array()
                            .add(cppbor::Map()  // protected
                                         .add(ALGORITHM, HMAC_256)
                                         .canonicalize()
                                         .encode())
                            .add(cppbor::Map())              // unprotected
                            .add(cborKeysToSign_.encode())   // payload
                            .add(std::move(keysToSignMac));  // tag

    auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
    ASSERT_TRUE(macPayload) << macPayload.message();
    checkProtectedData(testMode, cborKeysToSign_, keysToSignMac, protectedData);
}

/**
@@ -390,10 +332,9 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodMode) {
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto challenge = randomBytes(32);
    auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain,
                                                             challenge, &deviceInfo, &protectedData,
                                                             &keysToSignMac);
                                                             challenge_, &deviceInfo,
                                                             &protectedData, &keysToSignMac);
    ASSERT_FALSE(status.isOk());
    ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
}
@@ -408,9 +349,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) {
    bytevec keysToSignMac;
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto challenge = randomBytes(32);
    auto status = provisionable_->generateCertificateRequest(
            true /* testMode */, keysToSign_, eekChain_.chain, challenge, &deviceInfo,
            true /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo,
            &protectedData, &keysToSignMac);
    ASSERT_FALSE(status.isOk());
    ASSERT_EQ(status.getServiceSpecificError(),
@@ -428,8 +368,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) {
    DeviceInfo deviceInfo;
    ProtectedData protectedData;
    auto status = provisionable_->generateCertificateRequest(
            false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */,
            &deviceInfo, &protectedData, &keysToSignMac);
            false /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo,
            &protectedData, &keysToSignMac);
    ASSERT_FALSE(status.isOk());
    ASSERT_EQ(status.getServiceSpecificError(),
              BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);