dac revocation: Default implementation to check if DAC chain is revoked (#33651)
* dac revocation: default implementation of CheckForRevokedDACChain
* option to configure the revocation set file in chip-tool
* Added few comments
* restyle
* add fstream to allow list of DefaultDeviceAttestationVerifier
* Address comments
Added an interface for device attestation revocation and the test
implementation for the same.
* error code if dac and pai both are revoked
* unit tests
* Update examples/chip-tool/commands/common/CredentialIssuerCommands.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Move setting of revocation delegate to default verifier
* factor out getting of revocation delegate
* Restyled by clang-format
* address reviews
* API to clear revocation set path, and minor cleanup and added a comment
to explain the usage of --dac-revocation-set-path argument
* Restyled by clang-format
* add some details about json schema
* Restyled by whitespace
* Add the help text in the argument
* Address review comments and added some TODOs
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn
index b5917f3..94e1eeb 100644
--- a/examples/chip-tool/BUILD.gn
+++ b/examples/chip-tool/BUILD.gn
@@ -109,6 +109,7 @@
"${chip_root}/src/app/tests/suites/commands/interaction_model",
"${chip_root}/src/controller/data_model",
"${chip_root}/src/credentials:file_attestation_trust_store",
+ "${chip_root}/src/credentials:test_dac_revocation_delegate",
"${chip_root}/src/lib",
"${chip_root}/src/lib/core:types",
"${chip_root}/src/lib/support/jsontlv",
diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp
index 7e871f8..1c3df51 100644
--- a/examples/chip-tool/commands/common/CHIPCommand.cpp
+++ b/examples/chip-tool/commands/common/CHIPCommand.cpp
@@ -21,6 +21,7 @@
#include <commands/icd/ICDCommand.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <credentials/attestation_verifier/FileAttestationTrustStore.h>
+#include <credentials/attestation_verifier/TestDACRevocationDelegateImpl.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/CHIPVendorIdentifiers.hpp>
#include <lib/support/CodeUtils.h>
@@ -48,7 +49,9 @@
constexpr char kPAATrustStorePathVariable[] = "CHIPTOOL_PAA_TRUST_STORE_PATH";
constexpr char kCDTrustStorePathVariable[] = "CHIPTOOL_CD_TRUST_STORE_PATH";
-const chip::Credentials::AttestationTrustStore * CHIPCommand::sTrustStore = nullptr;
+const chip::Credentials::AttestationTrustStore * CHIPCommand::sTrustStore = nullptr;
+chip::Credentials::DeviceAttestationRevocationDelegate * CHIPCommand::sRevocationDelegate = nullptr;
+
chip::Credentials::GroupDataProviderImpl CHIPCommand::sGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric };
// All fabrics share the same ICD client storage.
chip::app::DefaultICDClientStorage CHIPCommand::sICDClientStorage;
@@ -87,6 +90,20 @@
return CHIP_NO_ERROR;
}
+CHIP_ERROR GetAttestationRevocationDelegate(const char * revocationSetPath,
+ chip::Credentials::DeviceAttestationRevocationDelegate ** revocationDelegate)
+{
+ if (revocationSetPath == nullptr)
+ {
+ return CHIP_NO_ERROR;
+ }
+
+ static chip::Credentials::TestDACRevocationDelegateImpl testDacRevocationDelegate;
+ ReturnErrorOnFailure(testDacRevocationDelegate.SetDeviceAttestationRevocationSetPath(revocationSetPath));
+ *revocationDelegate = &testDacRevocationDelegate;
+ return CHIP_NO_ERROR;
+}
+
} // namespace
CHIP_ERROR CHIPCommand::MaybeSetUpStack()
@@ -151,6 +168,8 @@
ReturnErrorOnFailure(GetAttestationTrustStore(mPaaTrustStorePath.ValueOr(nullptr), &sTrustStore));
+ ReturnLogErrorOnFailure(GetAttestationRevocationDelegate(mDacRevocationSetPath.ValueOr(nullptr), &sRevocationDelegate));
+
auto engine = chip::app::InteractionModelEngine::GetInstance();
VerifyOrReturnError(engine != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnLogErrorOnFailure(ChipToolCheckInDelegate()->Init(&sICDClientStorage, engine));
@@ -450,7 +469,7 @@
std::unique_ptr<ChipDeviceCommissioner> commissioner = std::make_unique<ChipDeviceCommissioner>();
chip::Controller::SetupParams commissionerParams;
- ReturnLogErrorOnFailure(mCredIssuerCmds->SetupDeviceAttestation(commissionerParams, sTrustStore));
+ ReturnLogErrorOnFailure(mCredIssuerCmds->SetupDeviceAttestation(commissionerParams, sTrustStore, sRevocationDelegate));
chip::Crypto::P256Keypair ephemeralKey;
diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h
index 50ab851..b48455e 100644
--- a/examples/chip-tool/commands/common/CHIPCommand.h
+++ b/examples/chip-tool/commands/common/CHIPCommand.h
@@ -86,6 +86,10 @@
AddArgument("only-allow-trusted-cd-keys", 0, 1, &mOnlyAllowTrustedCdKeys,
"Only allow trusted CD verifying keys (disallow test keys). If not provided or 0 (\"false\"), untrusted CD "
"verifying keys are allowed. If 1 (\"true\"), test keys are disallowed.");
+ AddArgument("dac-revocation-set-path", &mDacRevocationSetPath,
+ "Path to JSON file containing the device attestation revocation set. "
+ "This argument caches the path to the revocation set. Once set, this will be used by all commands in "
+ "interactive mode.");
#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
AddArgument("trace_file", &mTraceFile);
AddArgument("trace_log", 0, 1, &mTraceLog);
@@ -222,11 +226,16 @@
chip::Optional<char *> mCDTrustStorePath;
chip::Optional<bool> mUseMaxSizedCerts;
chip::Optional<bool> mOnlyAllowTrustedCdKeys;
+ chip::Optional<char *> mDacRevocationSetPath;
// Cached trust store so commands other than the original startup command
// can spin up commissioners as needed.
static const chip::Credentials::AttestationTrustStore * sTrustStore;
+ // Cached DAC revocation delegate, this can be set using "--dac-revocation-set-path" argument
+ // Once set this will be used by all commands.
+ static chip::Credentials::DeviceAttestationRevocationDelegate * sRevocationDelegate;
+
static void RunQueuedCommand(intptr_t commandArg);
typedef decltype(RunQueuedCommand) MatterWorkCallback;
static void RunCommandCleanup(intptr_t commandArg);
diff --git a/examples/chip-tool/commands/common/CredentialIssuerCommands.h b/examples/chip-tool/commands/common/CredentialIssuerCommands.h
index fd096b3..f8e225a 100644
--- a/examples/chip-tool/commands/common/CredentialIssuerCommands.h
+++ b/examples/chip-tool/commands/common/CredentialIssuerCommands.h
@@ -57,10 +57,13 @@
* Verifier.
* @param[in] trustStore A pointer to the PAA trust store to use to find valid PAA roots.
*
+ * @param[in] revocationDelegate A pointer to the Device Attestation Revocation Delegate for checking revoked DACs and PAIs.
+ *
* @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
*/
virtual CHIP_ERROR SetupDeviceAttestation(chip::Controller::SetupParams & setupParams,
- const chip::Credentials::AttestationTrustStore * trustStore) = 0;
+ const chip::Credentials::AttestationTrustStore * trustStore,
+ chip::Credentials::DeviceAttestationRevocationDelegate * revocationDelegate) = 0;
/**
* @brief Add a list of additional non-default CD verifying keys (by certificate)
diff --git a/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h b/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h
index a23b45e..495ae8d 100644
--- a/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h
+++ b/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h
@@ -34,16 +34,18 @@
return mOpCredsIssuer.Initialize(storage);
}
CHIP_ERROR SetupDeviceAttestation(chip::Controller::SetupParams & setupParams,
- const chip::Credentials::AttestationTrustStore * trustStore) override
+ const chip::Credentials::AttestationTrustStore * trustStore,
+ chip::Credentials::DeviceAttestationRevocationDelegate * revocationDelegate) override
{
chip::Credentials::SetDeviceAttestationCredentialsProvider(chip::Credentials::Examples::GetExampleDACProvider());
- mDacVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore);
+ mDacVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore, revocationDelegate);
setupParams.deviceAttestationVerifier = mDacVerifier;
mDacVerifier->EnableCdTestKeySupport(mAllowTestCdSigningKey);
return CHIP_NO_ERROR;
}
+
chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() override { return &mOpCredsIssuer; }
void SetCredentialIssuerCATValues(chip::CATValues cats) override { mOpCredsIssuer.SetCATValuesForNextNOCRequest(cats); }
CHIP_ERROR GenerateControllerNOCChain(chip::NodeId nodeId, chip::FabricId fabricId, const chip::CATValues & cats,
diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index d897c86..8790faf 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -139,6 +139,7 @@
'src/credentials/attestation_verifier/FileAttestationTrustStore.h': {'vector'},
'src/credentials/attestation_verifier/FileAttestationTrustStore.cpp': {'string'},
+ 'src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp': {'fstream'},
'src/setup_payload/AdditionalDataPayload.h': {'string'},
'src/setup_payload/AdditionalDataPayloadParser.cpp': {'vector', 'string'},
diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn
index df7afc0..670cefc 100644
--- a/src/credentials/BUILD.gn
+++ b/src/credentials/BUILD.gn
@@ -13,6 +13,7 @@
# limitations under the License.
import("//build_overrides/chip.gni")
+import("//build_overrides/jsoncpp.gni")
import("//build_overrides/nlassert.gni")
import("${chip_root}/src/crypto/crypto.gni")
import("${chip_root}/src/lib/core/core.gni")
@@ -185,3 +186,17 @@
"${nlassert_root}:nlassert",
]
}
+
+static_library("test_dac_revocation_delegate") {
+ output_name = "libTestDACRevocationDelegate"
+
+ sources = [
+ "attestation_verifier/TestDACRevocationDelegateImpl.cpp",
+ "attestation_verifier/TestDACRevocationDelegateImpl.h",
+ ]
+
+ public_deps = [
+ ":credentials",
+ jsoncpp_root,
+ ]
+}
diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp
index f3444b0..14759d8 100644
--- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp
+++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp
@@ -610,11 +610,14 @@
void DefaultDACVerifier::CheckForRevokedDACChain(const AttestationInfo & info,
Callback::Callback<OnAttestationInformationVerification> * onCompletion)
{
- AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
-
- // TODO(#33124): Implement default version of CheckForRevokedDACChain
-
- onCompletion->mCall(onCompletion->mContext, info, attestationError);
+ if (mRevocationDelegate != nullptr)
+ {
+ mRevocationDelegate->CheckForRevokedDACChain(info, onCompletion);
+ }
+ else
+ {
+ onCompletion->mCall(onCompletion->mContext, info, AttestationVerificationResult::kSuccess);
+ }
}
bool CsaCdKeysTrustStore::IsCdTestKey(const ByteSpan & kid) const
@@ -693,9 +696,10 @@
return &gTestAttestationTrustStore.get();
}
-DeviceAttestationVerifier * GetDefaultDACVerifier(const AttestationTrustStore * paaRootStore)
+DeviceAttestationVerifier * GetDefaultDACVerifier(const AttestationTrustStore * paaRootStore,
+ DeviceAttestationRevocationDelegate * revocationDelegate)
{
- static DefaultDACVerifier defaultDACVerifier{ paaRootStore };
+ static DefaultDACVerifier defaultDACVerifier{ paaRootStore, revocationDelegate };
return &defaultDACVerifier;
}
diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h
index 346d098..7e0fc1c 100644
--- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h
+++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h
@@ -59,6 +59,10 @@
public:
DefaultDACVerifier(const AttestationTrustStore * paaRootStore) : mAttestationTrustStore(paaRootStore) {}
+ DefaultDACVerifier(const AttestationTrustStore * paaRootStore, DeviceAttestationRevocationDelegate * revocationDelegate) :
+ mAttestationTrustStore(paaRootStore), mRevocationDelegate(revocationDelegate)
+ {}
+
void VerifyAttestationInformation(const DeviceAttestationVerifier::AttestationInfo & info,
Callback::Callback<OnAttestationInformationVerification> * onCompletion) override;
@@ -79,11 +83,17 @@
CsaCdKeysTrustStore * GetCertificationDeclarationTrustStore() override { return &mCdKeysTrustStore; }
+ void SetRevocationDelegate(DeviceAttestationRevocationDelegate * revocationDelegate)
+ {
+ mRevocationDelegate = revocationDelegate;
+ }
+
protected:
DefaultDACVerifier() {}
CsaCdKeysTrustStore mCdKeysTrustStore;
const AttestationTrustStore * mAttestationTrustStore;
+ DeviceAttestationRevocationDelegate * mRevocationDelegate = nullptr;
};
/**
@@ -112,7 +122,8 @@
* process lifetime. In particular, after the first call it's not
* possible to change which AttestationTrustStore is used by this verifier.
*/
-DeviceAttestationVerifier * GetDefaultDACVerifier(const AttestationTrustStore * paaRootStore);
+DeviceAttestationVerifier * GetDefaultDACVerifier(const AttestationTrustStore * paaRootStore,
+ DeviceAttestationRevocationDelegate * revocationDelegate = nullptr);
} // namespace Credentials
} // namespace chip
diff --git a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h
index f45ceae..e691593 100644
--- a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h
+++ b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h
@@ -47,6 +47,7 @@
kPaiVendorIdMismatch = 205,
kPaiAuthorityNotFound = 206,
kPaiMissing = 207,
+ kPaiAndDacRevoked = 208,
kDacExpired = 300,
kDacSignatureInvalid = 301,
@@ -419,6 +420,28 @@
};
/**
+ * @brief Interface for checking the device attestation revocation status
+ *
+ */
+class DeviceAttestationRevocationDelegate
+{
+public:
+ DeviceAttestationRevocationDelegate() = default;
+ virtual ~DeviceAttestationRevocationDelegate() = default;
+
+ /**
+ * @brief Verify whether or not the given DAC chain is revoked.
+ *
+ * @param[in] info All of the information required to check for revoked DAC chain.
+ * @param[in] onCompletion Callback handler to provide Attestation Information Verification result to the caller of
+ * CheckForRevokedDACChain().
+ */
+ virtual void
+ CheckForRevokedDACChain(const DeviceAttestationVerifier::AttestationInfo & info,
+ Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion) = 0;
+};
+
+/**
* Instance getter for the global DeviceAttestationVerifier.
*
* Callers have to externally synchronize usage of this function.
diff --git a/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp b/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp
new file mode 100644
index 0000000..4e19785
--- /dev/null
+++ b/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp
@@ -0,0 +1,219 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
+#include <credentials/attestation_verifier/TestDACRevocationDelegateImpl.h>
+#include <lib/support/Base64.h>
+#include <lib/support/BytesToHex.h>
+#include <lib/support/logging/CHIPLogging.h>
+
+#include <fstream>
+#include <json/json.h>
+#include <string>
+
+using namespace chip::Crypto;
+
+namespace chip {
+namespace Credentials {
+
+namespace {
+CHIP_ERROR BytesToHexStr(const ByteSpan & bytes, MutableCharSpan & outHexStr)
+{
+ Encoding::HexFlags flags = Encoding::HexFlags::kUppercase;
+ ReturnErrorOnFailure(BytesToHex(bytes.data(), bytes.size(), outHexStr.data(), outHexStr.size(), flags));
+ outHexStr.reduce_size(2 * bytes.size());
+ return CHIP_NO_ERROR;
+}
+} // anonymous namespace
+
+CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationSetPath(std::string_view path)
+{
+ VerifyOrReturnError(path.empty() != true, CHIP_ERROR_INVALID_ARGUMENT);
+ mDeviceAttestationRevocationSetPath = path;
+ return CHIP_NO_ERROR;
+}
+
+void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
+{
+ // clear the string_view
+ mDeviceAttestationRevocationSetPath = mDeviceAttestationRevocationSetPath.substr(0, 0);
+}
+
+// This method parses the below JSON Scheme
+// [
+// {
+// "type": "revocation_set",
+// "issuer_subject_key_id": "<issuer subject key ID as uppercase hex, 20 bytes>",
+// "issuer_name": "<ASN.1 SEQUENCE of Issuer of the CRL as base64>",
+// "revoked_serial_numbers: [
+// "serial1 bytes as base64",
+// "serial2 bytes as base64"
+// ]
+// }
+// ]
+//
+bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const CharSpan & akidHexStr, const CharSpan & issuerNameBase64Str,
+ const CharSpan & serialNumberHexStr)
+{
+ std::ifstream file(mDeviceAttestationRevocationSetPath.data());
+ if (!file.is_open())
+ {
+ ChipLogError(NotSpecified, "Failed to open file: %s", mDeviceAttestationRevocationSetPath.data());
+ return false;
+ }
+
+ // Parse the JSON data incrementally
+ Json::CharReaderBuilder readerBuilder;
+ Json::Value jsonData;
+ std::string errs;
+
+ bool parsingSuccessful = Json::parseFromStream(readerBuilder, file, &jsonData, &errs);
+
+ // Close the file as it's no longer needed
+ file.close();
+
+ if (!parsingSuccessful)
+ {
+ ChipLogError(NotSpecified, "Failed to parse JSON: %s", errs.c_str());
+ return false;
+ }
+
+ std::string issuerName = std::string(issuerNameBase64Str.data(), issuerNameBase64Str.size());
+ std::string serialNumber = std::string(serialNumberHexStr.data(), serialNumberHexStr.size());
+ std::string akid = std::string(akidHexStr.data(), akidHexStr.size());
+
+ for (const auto & revokedSet : jsonData)
+ {
+ if (revokedSet["issuer_name"].asString() != issuerName)
+ {
+ continue;
+ }
+ if (revokedSet["issuer_subject_key_id"].asString() != akid)
+ {
+ continue;
+ }
+ for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
+ {
+ if (revokedSerialNumber.asString() == serialNumber)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outAKIDHexStr)
+{
+ uint8_t akidBuf[kAuthorityKeyIdentifierLength];
+ MutableByteSpan akid(akidBuf);
+
+ ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, akid));
+
+ return BytesToHexStr(akid, outAKIDHexStr);
+}
+
+CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, MutableCharSpan & outSerialNumberHexStr)
+{
+ uint8_t serialNumberBuf[kMaxCertificateSerialNumberLength] = { 0 };
+ MutableByteSpan serialNumber(serialNumberBuf);
+
+ ReturnErrorOnFailure(ExtractSerialNumberFromX509Cert(certDer, serialNumber));
+ return BytesToHexStr(serialNumber, outSerialNumberHexStr);
+}
+
+CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer,
+ MutableCharSpan & outIssuerNameBase64String)
+{
+ uint8_t issuerBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
+ MutableByteSpan issuer(issuerBuf);
+
+ ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, issuer));
+ VerifyOrReturnError(outIssuerNameBase64String.size() >= BASE64_ENCODED_LEN(issuer.size()), CHIP_ERROR_BUFFER_TOO_SMALL);
+
+ uint16_t encodedLen = Base64Encode(issuer.data(), static_cast<uint16_t>(issuer.size()), outIssuerNameBase64String.data());
+ outIssuerNameBase64String.reduce_size(encodedLen);
+ return CHIP_NO_ERROR;
+}
+
+bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
+{
+ static constexpr uint32_t maxIssuerBase64Len = BASE64_ENCODED_LEN(kMaxCertificateDistinguishedNameLength);
+
+ char issuerNameBuffer[maxIssuerBase64Len] = { 0 };
+ char serialNumberHexStrBuffer[2 * kMaxCertificateSerialNumberLength] = { 0 };
+ char akidHexStrBuffer[2 * kAuthorityKeyIdentifierLength] = { 0 };
+
+ MutableCharSpan issuerName(issuerNameBuffer);
+ MutableCharSpan serialNumber(serialNumberHexStrBuffer);
+ MutableCharSpan akid(akidHexStrBuffer);
+
+ VerifyOrReturnValue(CHIP_NO_ERROR == GetIssuerNameBase64Str(certDer, issuerName), false);
+ ChipLogDetail(NotSpecified, "Issuer: %.*s", static_cast<int>(issuerName.size()), issuerName.data());
+
+ VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false);
+ ChipLogDetail(NotSpecified, "Serial Number: %.*s", static_cast<int>(serialNumber.size()), serialNumber.data());
+
+ VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false);
+ ChipLogDetail(NotSpecified, "AKID: %.*s", static_cast<int>(akid.size()), akid.data());
+
+ // TODO: Cross-validate the CRLSignerCertificate and CRLSignerDelegator per spec: #34587
+
+ return IsEntryInRevocationSet(akid, issuerName, serialNumber);
+}
+
+void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(
+ const DeviceAttestationVerifier::AttestationInfo & info,
+ Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion)
+{
+ AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
+
+ if (mDeviceAttestationRevocationSetPath.empty())
+ {
+
+ onCompletion->mCall(onCompletion->mContext, info, attestationError);
+ }
+
+ ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.data());
+
+ if (IsCertificateRevoked(info.dacDerBuffer))
+ {
+ ChipLogProgress(NotSpecified, "Found revoked DAC in %s", mDeviceAttestationRevocationSetPath.data());
+ attestationError = AttestationVerificationResult::kDacRevoked;
+ }
+
+ ChipLogDetail(NotSpecified, "Checking for revoked PAI in %s", mDeviceAttestationRevocationSetPath.data());
+
+ if (IsCertificateRevoked(info.paiDerBuffer))
+ {
+ ChipLogProgress(NotSpecified, "Found revoked PAI in %s", mDeviceAttestationRevocationSetPath.data());
+
+ if (attestationError == AttestationVerificationResult::kDacRevoked)
+ {
+ attestationError = AttestationVerificationResult::kPaiAndDacRevoked;
+ }
+ else
+ {
+ attestationError = AttestationVerificationResult::kPaiRevoked;
+ }
+ }
+
+ onCompletion->mCall(onCompletion->mContext, info, attestationError);
+}
+
+} // namespace Credentials
+} // namespace chip
diff --git a/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.h b/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.h
new file mode 100644
index 0000000..c820e56
--- /dev/null
+++ b/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
+#include <lib/support/Span.h>
+#include <string_view>
+
+namespace chip {
+namespace Credentials {
+
+class TestDACRevocationDelegateImpl : public DeviceAttestationRevocationDelegate
+{
+public:
+ TestDACRevocationDelegateImpl() = default;
+ ~TestDACRevocationDelegateImpl() = default;
+
+ /**
+ * @brief Verify whether or not the given DAC chain is revoked.
+ *
+ * @param[in] info All of the information required to check for revoked DAC chain.
+ * @param[in] onCompletion Callback handler to provide Attestation Information Verification result to the caller of
+ * CheckForRevokedDACChain().
+ */
+ void CheckForRevokedDACChain(
+ const DeviceAttestationVerifier::AttestationInfo & info,
+ Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion) override;
+
+ // Set the path to the device attestation revocation set JSON file.
+ // revocation set can be generated using credentials/generate-revocation-set.py script
+ // This API returns CHIP_ERROR_INVALID_ARGUMENT if the path is null.
+ CHIP_ERROR SetDeviceAttestationRevocationSetPath(std::string_view path);
+
+ // Clear the path to the device attestation revocation set JSON file.
+ // This can be used to skip the revocation check
+ void ClearDeviceAttestationRevocationSetPath();
+
+private:
+ CHIP_ERROR GetAKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outAKIDHexStr);
+ CHIP_ERROR GetSerialNumberHexStr(const ByteSpan & certDer, MutableCharSpan & outSerialNumberHexStr);
+ CHIP_ERROR GetIssuerNameBase64Str(const ByteSpan & certDer, MutableCharSpan & outIssuerNameBase64String);
+ bool IsEntryInRevocationSet(const CharSpan & akidHexStr, const CharSpan & issuerNameBase64Str,
+ const CharSpan & serialNumberHexStr);
+ bool IsCertificateRevoked(const ByteSpan & certDer);
+
+ std::string_view mDeviceAttestationRevocationSetPath;
+};
+
+} // namespace Credentials
+} // namespace chip
diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn
index 99cb1e8..393b246 100644
--- a/src/credentials/tests/BUILD.gn
+++ b/src/credentials/tests/BUILD.gn
@@ -68,6 +68,7 @@
"${chip_root}/src/controller:controller",
"${chip_root}/src/credentials",
"${chip_root}/src/credentials:default_attestation_verifier",
+ "${chip_root}/src/credentials:test_dac_revocation_delegate",
"${chip_root}/src/lib/core",
"${chip_root}/src/lib/core:string-builder-adapters",
"${chip_root}/src/lib/support:testing",
diff --git a/src/credentials/tests/TestDeviceAttestationCredentials.cpp b/src/credentials/tests/TestDeviceAttestationCredentials.cpp
index 85a5d4e..0f80df9 100644
--- a/src/credentials/tests/TestDeviceAttestationCredentials.cpp
+++ b/src/credentials/tests/TestDeviceAttestationCredentials.cpp
@@ -25,6 +25,7 @@
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h>
#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
+#include <credentials/attestation_verifier/TestDACRevocationDelegateImpl.h>
#include <credentials/attestation_verifier/TestPAAStore.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
#include <credentials/examples/ExampleDACs.h>
@@ -37,6 +38,8 @@
#include "CHIPAttCert_test_vectors.h"
+#include <fstream>
+
using namespace chip;
using namespace chip::Crypto;
using namespace chip::Credentials;
@@ -413,3 +416,160 @@
}
}
}
+
+static void WriteTestRevokedData(const char * jsonData, const char * fileName)
+{
+ // TODO: Add option to load test data from the test without using file. #34588
+
+ // write data to /tmp/sample_revoked_set.json using fstream APIs
+ std::ofstream file;
+ file.open(fileName, std::ofstream::out | std::ofstream::trunc);
+ file << jsonData;
+ file.close();
+}
+
+TEST_F(TestDeviceAttestationCredentials, TestDACRevocationDelegateImpl)
+{
+ uint8_t attestationElementsTestVector[] = { 0 };
+ uint8_t attestationChallengeTestVector[] = { 0 };
+ uint8_t attestationSignatureTestVector[] = { 0 };
+ uint8_t attestationNonceTestVector[] = { 0 };
+
+ // Details for TestCerts::sTestCert_DAC_FFF1_8000_0004_Cert
+ // Issuer: MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw
+ // AKID: AF42B7094DEBD515EC6ECF33B81115225F325288
+ // Serial Number: 0C694F7F866067B2
+ //
+ // Details for TestCerts::sTestCert_PAI_FFF1_8000_Cert
+ // Issuer: MDAxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEUMBIGCisGAQQBgqJ8AgEMBEZGRjE=
+ // AKID: 6AFD22771F511FECBF1641976710DCDC31A1717E
+ // Serial Number: 3E6CE6509AD840CD1
+ Credentials::DeviceAttestationVerifier::AttestationInfo info(
+ ByteSpan(attestationElementsTestVector), ByteSpan(attestationChallengeTestVector), ByteSpan(attestationSignatureTestVector),
+ TestCerts::sTestCert_PAI_FFF1_8000_Cert, TestCerts::sTestCert_DAC_FFF1_8000_0004_Cert, ByteSpan(attestationNonceTestVector),
+ static_cast<VendorId>(0xFFF1), 0x8000);
+
+ AttestationVerificationResult attestationResult = AttestationVerificationResult::kNotImplemented;
+
+ Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> attestationInformationVerificationCallback(
+ OnAttestationInformationVerificationCallback, &attestationResult);
+
+ TestDACRevocationDelegateImpl revocationDelegateImpl;
+
+ // Test without revocation set
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ const char * tmpJsonFile = "/tmp/sample_revoked_set.json";
+ revocationDelegateImpl.SetDeviceAttestationRevocationSetPath(tmpJsonFile);
+
+ // Test empty json
+ WriteTestRevokedData("", tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ // Test DAC is revoked
+ const char * jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "AF42B7094DEBD515EC6ECF33B81115225F325288",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["0C694F7F866067B2"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kDacRevoked);
+
+ // Test PAI is revoked
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "6AFD22771F511FECBF1641976710DCDC31A1717E",
+ "issuer_name": "MDAxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEUMBIGCisGAQQBgqJ8AgEMBEZGRjE=",
+ "revoked_serial_numbers": ["3E6CE6509AD840CD"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kPaiRevoked);
+
+ // Test DAC and PAI both revoked
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "AF42B7094DEBD515EC6ECF33B81115225F325288",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["0C694F7F866067B2"]
+ },
+ {
+ "type": "revocation_set",
+ "issuer_subject_key_id": "6AFD22771F511FECBF1641976710DCDC31A1717E",
+ "issuer_name": "MDAxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEUMBIGCisGAQQBgqJ8AgEMBEZGRjE=",
+ "revoked_serial_numbers": ["3E6CE6509AD840CD"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kPaiAndDacRevoked);
+
+ // Test with another test DAC and PAI
+ Credentials::DeviceAttestationVerifier::AttestationInfo FFF2_8001_info(
+ ByteSpan(attestationElementsTestVector), ByteSpan(attestationChallengeTestVector), ByteSpan(attestationSignatureTestVector),
+ TestCerts::sTestCert_PAI_FFF2_8001_Cert, TestCerts::sTestCert_DAC_FFF2_8001_0008_Cert, ByteSpan(attestationNonceTestVector),
+ static_cast<VendorId>(0xFFF2), 0x8001);
+ revocationDelegateImpl.CheckForRevokedDACChain(FFF2_8001_info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ // Test issuer does not match
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "BF42B7094DEBD515EC6ECF33B81115225F325289",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["0C694F7F866067B2"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ // Test subject key ID does not match
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "BF42B7094DEBD515EC6ECF33B81115225F325289",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["0C694F7F866067B2"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ // Test serial number does not match
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "AF42B7094DEBD515EC6ECF33B81115225F325288",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["3E6CE6509AD840CD1", "BC694F7F866067B1"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+
+ // Test starting serial number bytes match but not all
+ jsonData = R"(
+ [{
+ "type": "revocation_set",
+ "issuer_subject_key_id": "AF42B7094DEBD515EC6ECF33B81115225F325288",
+ "issuer_name": "MEYxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBSTEUMBIGCisGAQQBgqJ8AgEMBEZGRjExFDASBgorBgEEAYKifAICDAQ4MDAw",
+ "revoked_serial_numbers": ["0C694F7F866067B21234"]
+ }]
+ )";
+ WriteTestRevokedData(jsonData, tmpJsonFile);
+ revocationDelegateImpl.CheckForRevokedDACChain(info, &attestationInformationVerificationCallback);
+ EXPECT_EQ(attestationResult, AttestationVerificationResult::kSuccess);
+}