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);
+}