diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index b5917f3bfaca62..94e1eebbde7508 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -109,6 +109,7 @@ static_library("chip-tool-utils") { "${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 7e871f8e781e14..1c3df517bd89d0 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,9 @@ constexpr chip::FabricId kIdentityOtherFabricId = 4; 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 @@ CHIP_ERROR GetAttestationTrustStore(const char * paaTrustStorePath, const chip:: 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 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() 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 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(CommissionerIdentity & identity, std::unique_ptr commissioner = std::make_unique(); 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 50ab851d284502..b48455ebed6821 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -86,6 +86,10 @@ class CHIPCommand : public Command 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 @@ class CHIPCommand : public Command chip::Optional mCDTrustStorePath; chip::Optional mUseMaxSizedCerts; chip::Optional mOnlyAllowTrustedCdKeys; + chip::Optional 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 fd096b31835723..f8e225afec4c5e 100644 --- a/examples/chip-tool/commands/common/CredentialIssuerCommands.h +++ b/examples/chip-tool/commands/common/CredentialIssuerCommands.h @@ -57,10 +57,13 @@ class CredentialIssuerCommands * 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 a23b45eae10627..495ae8d7a544d6 100644 --- a/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h +++ b/examples/chip-tool/commands/example/ExampleCredentialIssuerCommands.h @@ -34,16 +34,18 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands 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 20c853b9906df8..2d0152a02c808b 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -137,6 +137,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 df7afc0c1025ba..670cefcd209c10 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 @@ static_library("file_attestation_trust_store") { "${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 f3444b0c303940..14759d850ffc40 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp @@ -610,11 +610,14 @@ CHIP_ERROR DefaultDACVerifier::VerifyNodeOperationalCSRInformation(const ByteSpa void DefaultDACVerifier::CheckForRevokedDACChain(const AttestationInfo & info, Callback::Callback * 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 @@ const AttestationTrustStore * GetTestAttestationTrustStore() 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 346d098a58a337..7e0fc1c4378848 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h @@ -59,6 +59,10 @@ class DefaultDACVerifier : public DeviceAttestationVerifier 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 * onCompletion) override; @@ -79,11 +83,17 @@ class DefaultDACVerifier : public DeviceAttestationVerifier 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 @@ const AttestationTrustStore * GetTestAttestationTrustStore(); * 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 f45ceae06c23fe..e6915931a73b68 100644 --- a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h +++ b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h @@ -47,6 +47,7 @@ enum class AttestationVerificationResult : uint16_t kPaiVendorIdMismatch = 205, kPaiAuthorityNotFound = 206, kPaiMissing = 207, + kPaiAndDacRevoked = 208, kDacExpired = 300, kDacSignatureInvalid = 301, @@ -418,6 +419,28 @@ class DeviceAttestationVerifier bool mEnableCdTestKeySupport = true; }; +/** + * @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 * onCompletion) = 0; +}; + /** * Instance getter for the global DeviceAttestationVerifier. * diff --git a/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp b/src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp new file mode 100644 index 00000000000000..4e1978525e7327 --- /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 +#include +#include +#include +#include + +#include +#include +#include + +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_name": "", +// "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(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(issuerName.size()), issuerName.data()); + + VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false); + ChipLogDetail(NotSpecified, "Serial Number: %.*s", static_cast(serialNumber.size()), serialNumber.data()); + + VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false); + ChipLogDetail(NotSpecified, "AKID: %.*s", static_cast(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 * 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 00000000000000..c820e56f5f6ce3 --- /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 +#include +#include + +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 * 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 99cb1e8e20d322..393b246ef20ee3 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -68,6 +68,7 @@ chip_test_suite("tests") { "${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 85a5d4edfa58a2..0f80df9fa9f6f4 100644 --- a/src/credentials/tests/TestDeviceAttestationCredentials.cpp +++ b/src/credentials/tests/TestDeviceAttestationCredentials.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,8 @@ #include "CHIPAttCert_test_vectors.h" +#include + using namespace chip; using namespace chip::Crypto; using namespace chip::Credentials; @@ -413,3 +416,160 @@ TEST_F(TestDeviceAttestationCredentials, TestAttestationTrustStore) } } } + +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(0xFFF1), 0x8000); + + AttestationVerificationResult attestationResult = AttestationVerificationResult::kNotImplemented; + + Callback::Callback 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(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); +}