Matter SDK Coverage Report
Current view: top level - credentials/attestation_verifier - TestDACRevocationDelegateImpl.cpp (source / functions) Coverage Total Hit
Test: SHA:7c9b1260e3daa86aae0d41b894469b295eee70e8 Lines: 83.1 % 148 123
Test Date: 2025-09-07 07:12:04 Functions: 87.5 % 16 14

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2024 Project CHIP Authors
       4              :  *
       5              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       6              :  *    you may not use this file except in compliance with the License.
       7              :  *    You may obtain a copy of the License at
       8              :  *
       9              :  *        http://www.apache.org/licenses/LICENSE-2.0
      10              :  *
      11              :  *    Unless required by applicable law or agreed to in writing, software
      12              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      13              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      14              :  *    See the License for the specific language governing permissions and
      15              :  *    limitations under the License.
      16              :  */
      17              : 
      18              : #include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
      19              : #include <credentials/attestation_verifier/TestDACRevocationDelegateImpl.h>
      20              : #include <lib/support/Base64.h>
      21              : #include <lib/support/BytesToHex.h>
      22              : #include <lib/support/logging/CHIPLogging.h>
      23              : 
      24              : #include <algorithm>
      25              : #include <fstream>
      26              : #include <json/json.h>
      27              : 
      28              : using namespace chip::Crypto;
      29              : 
      30              : namespace chip {
      31              : namespace Credentials {
      32              : 
      33              : namespace {
      34              : 
      35           55 : CHIP_ERROR BytesToHexStr(const ByteSpan & bytes, std::string & outHexStr)
      36              : {
      37           55 :     size_t hexLength = bytes.size() * 2;
      38           55 :     outHexStr.resize(hexLength);
      39              : 
      40           55 :     Encoding::HexFlags flags = Encoding::HexFlags::kUppercase;
      41           55 :     return BytesToHex(bytes.data(), bytes.size(), &outHexStr[0], hexLength, flags);
      42              : }
      43              : 
      44              : } // anonymous namespace
      45              : 
      46            0 : CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationSetPath(std::string_view path)
      47              : {
      48            0 :     VerifyOrReturnError(path.empty() != true, CHIP_ERROR_INVALID_ARGUMENT);
      49            0 :     mDeviceAttestationRevocationSetPath = path;
      50            0 :     return CHIP_NO_ERROR;
      51              : }
      52              : 
      53           12 : CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationData(const std::string & jsonData)
      54              : {
      55           12 :     mRevocationData = jsonData;
      56           12 :     return CHIP_NO_ERROR;
      57              : }
      58              : 
      59            0 : void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
      60              : {
      61              :     // clear the string_view
      62            0 :     mDeviceAttestationRevocationSetPath = mDeviceAttestationRevocationSetPath.substr(0, 0);
      63            0 : }
      64              : 
      65           12 : void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationData()
      66              : {
      67           12 :     mRevocationData.clear();
      68           12 : }
      69              : 
      70              : // Check if issuer and AKID matches with the crl signer OR crl signer delegator's subject and SKID
      71            8 : bool TestDACRevocationDelegateImpl::CrossValidateCert(const Json::Value & revokedSet, const std::string & akidHexStr,
      72              :                                                       const std::string & issuerNameBase64Str)
      73              : {
      74            8 :     std::string certBase64;
      75            8 :     [[maybe_unused]] std::string certType;
      76              : 
      77            8 :     if (revokedSet.isMember("crl_signer_delegator"))
      78              :     {
      79            1 :         certBase64 = revokedSet["crl_signer_delegator"].asString();
      80            1 :         certType   = "CRL Signer delegator";
      81              :     }
      82              :     else
      83              :     {
      84            7 :         certBase64 = revokedSet["crl_signer_cert"].asString();
      85            7 :         certType   = "CRL Signer";
      86              :     }
      87              : 
      88            8 :     uint8_t certDerBuf[kMax_x509_Certificate_Length] = { 0 };
      89            8 :     MutableByteSpan certDER(certDerBuf);
      90              : 
      91              :     // Verify we have enough room to store the decoded certificate
      92            8 :     size_t maxDecodeLen = BASE64_MAX_DECODED_LEN(certBase64.size());
      93            8 :     VerifyOrReturnValue(certDER.size() >= maxDecodeLen, false);
      94              : 
      95            8 :     uint16_t derLen = Base64Decode(certBase64.c_str(), static_cast<uint16_t>(certBase64.size()), certDER.data());
      96            8 :     VerifyOrReturnValue(derLen != UINT16_MAX, false);
      97            8 :     certDER.reduce_size(derLen);
      98              : 
      99            8 :     std::string subject;
     100            8 :     std::string keyId;
     101              : 
     102            8 :     VerifyOrReturnValue(CHIP_NO_ERROR == GetSubjectNameBase64Str(certDER, subject), false);
     103            7 :     VerifyOrReturnValue(CHIP_NO_ERROR == GetSKIDHexStr(certDER, keyId), false);
     104              : 
     105            7 :     ChipLogDetail(NotSpecified, "%s: Subject: %s", certType.c_str(), subject.c_str());
     106            7 :     ChipLogDetail(NotSpecified, "%s: SKID: %s", certType.c_str(), keyId.c_str());
     107              : 
     108            7 :     return (akidHexStr == keyId && issuerNameBase64Str == subject);
     109            8 : }
     110              : 
     111              : // This method parses the below JSON Scheme
     112              : // [
     113              : //   {
     114              : //     "type": "revocation_set",
     115              : //     "issuer_subject_key_id": "<issuer subject key ID as uppercase hex, 20 bytes>",
     116              : //     "issuer_name": "<ASN.1 SEQUENCE of Issuer of the CRL as base64>",
     117              : //     "revoked_serial_numbers: [
     118              : //       "serial1 bytes as base64",
     119              : //       "serial2 bytes as base64"
     120              : //     ]
     121              : //     "crl_signer_cert": "<base64 encoded DER certificate>",
     122              : //     "crl_signer_delegator": "<base64 encoded DER certificate>",
     123              : //   }
     124              : // ]
     125              : //
     126           24 : bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const std::string & akidHexStr, const std::string & issuerNameBase64Str,
     127              :                                                            const std::string & serialNumberHexStr)
     128              : {
     129           24 :     Json::Value jsonData;
     130              : 
     131              :     // Try direct data first, then fall back to file
     132           24 :     if (!mRevocationData.empty())
     133              :     {
     134           24 :         std::string errs;
     135           48 :         std::istringstream jsonStream(!mRevocationData.empty() ? mRevocationData : "[]");
     136           24 :         if (!Json::parseFromStream(Json::CharReaderBuilder(), jsonStream, &jsonData, &errs))
     137              :         {
     138            2 :             ChipLogError(NotSpecified, "Failed to parse JSON data: %s", errs.c_str());
     139            2 :             return false;
     140              :         }
     141           26 :     }
     142            0 :     else if (!mDeviceAttestationRevocationSetPath.empty())
     143              :     {
     144            0 :         std::string errs;
     145            0 :         std::ifstream file(mDeviceAttestationRevocationSetPath.c_str());
     146            0 :         if (!file.is_open())
     147              :         {
     148            0 :             ChipLogError(NotSpecified, "Failed to open file: %s", mDeviceAttestationRevocationSetPath.c_str());
     149            0 :             return false;
     150              :         }
     151              : 
     152            0 :         bool parsingSuccessful = Json::parseFromStream(Json::CharReaderBuilder(), file, &jsonData, &errs);
     153            0 :         file.close();
     154              : 
     155            0 :         if (!parsingSuccessful)
     156              :         {
     157            0 :             ChipLogError(NotSpecified, "Failed to parse JSON from file: %s", errs.c_str());
     158            0 :             return false;
     159              :         }
     160            0 :     }
     161              :     else
     162              :     {
     163            0 :         ChipLogProgress(NotSpecified, "No revocation data available");
     164              :         // No revocation data available
     165            0 :         return false;
     166              :     }
     167              : 
     168           22 :     VerifyOrReturnValue(jsonData.isArray(), false, ChipLogError(NotSpecified, "Revocation set is not a valid JSON Array"));
     169              : 
     170              :     // 6.2.4.2. Determining Revocation Status of an Entity
     171           37 :     for (const auto & revokedSet : jsonData)
     172              :     {
     173           29 :         VerifyOrReturnValue(revokedSet.isObject(), false,
     174              :                             ChipLogError(NotSpecified, "Revocation set entry is not a valid JSON object"));
     175              : 
     176           23 :         if (revokedSet["issuer_name"].asString() != issuerNameBase64Str)
     177              :         {
     178           13 :             continue;
     179              :         }
     180           10 :         if (revokedSet["issuer_subject_key_id"].asString() != akidHexStr)
     181              :         {
     182            2 :             continue;
     183              :         }
     184              : 
     185              :         // 4.a cross validate PAI with crl signer OR crl signer delegator
     186              :         // 4.b cross validate DAC with crl signer OR crl signer delegator
     187            8 :         VerifyOrReturnValue(CrossValidateCert(revokedSet, akidHexStr, issuerNameBase64Str), false);
     188              : 
     189              :         // 4.c check if serial number is revoked
     190           10 :         for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
     191              :         {
     192            8 :             if (revokedSerialNumber.asString() == serialNumberHexStr)
     193              :             {
     194            5 :                 return true;
     195              :             }
     196              :         }
     197              :     }
     198           14 :     return false;
     199           24 : }
     200              : 
     201           31 : CHIP_ERROR TestDACRevocationDelegateImpl::GetKeyIDHexStr(const ByteSpan & certDer, std::string & outKeyIDHexStr,
     202              :                                                          KeyIdType keyIdType)
     203              : {
     204              :     static_assert(kAuthorityKeyIdentifierLength == kSubjectKeyIdentifierLength, "AKID and SKID length mismatch");
     205              : 
     206              :     uint8_t keyIdBuf[kAuthorityKeyIdentifierLength];
     207           31 :     MutableByteSpan keyId(keyIdBuf);
     208              : 
     209           31 :     switch (keyIdType)
     210              :     {
     211           24 :     case KeyIdType::kAKID:
     212           24 :         ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, keyId));
     213           24 :         break;
     214              : 
     215            7 :     case KeyIdType::kSKID:
     216            7 :         ReturnErrorOnFailure(ExtractSKIDFromX509Cert(certDer, keyId));
     217            7 :         break;
     218              : 
     219            0 :     default:
     220            0 :         return CHIP_ERROR_INVALID_ARGUMENT;
     221              :     }
     222              : 
     223           31 :     return BytesToHexStr(keyId, outKeyIDHexStr);
     224              : }
     225              : 
     226           24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, std::string & outAKIDHexStr)
     227              : {
     228           24 :     return GetKeyIDHexStr(certDer, outAKIDHexStr, KeyIdType::kAKID);
     229              : }
     230              : 
     231            7 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSKIDHexStr(const ByteSpan & certDer, std::string & outSKIDHexStr)
     232              : {
     233            7 :     return GetKeyIDHexStr(certDer, outSKIDHexStr, KeyIdType::kSKID);
     234              : }
     235              : 
     236           24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, std::string & outSerialNumberHexStr)
     237              : {
     238           24 :     uint8_t serialNumberBuf[kMaxCertificateSerialNumberLength] = { 0 };
     239           24 :     MutableByteSpan serialNumber(serialNumberBuf);
     240              : 
     241           24 :     ReturnErrorOnFailure(ExtractSerialNumberFromX509Cert(certDer, serialNumber));
     242           24 :     return BytesToHexStr(serialNumber, outSerialNumberHexStr);
     243              : }
     244              : 
     245           32 : CHIP_ERROR TestDACRevocationDelegateImpl::GetRDNBase64Str(const ByteSpan & certDer, std::string & outRDNBase64String,
     246              :                                                           RDNType rdnType)
     247              : {
     248           32 :     uint8_t rdnBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
     249           32 :     MutableByteSpan rdn(rdnBuf);
     250              : 
     251           32 :     switch (rdnType)
     252              :     {
     253           24 :     case RDNType::kIssuer:
     254           24 :         ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, rdn));
     255           24 :         break;
     256              : 
     257            8 :     case RDNType::kSubject:
     258            8 :         ReturnErrorOnFailure(ExtractSubjectFromX509Cert(certDer, rdn));
     259            7 :         break;
     260              : 
     261            0 :     default:
     262            0 :         return CHIP_ERROR_INVALID_ARGUMENT;
     263              :     }
     264              : 
     265              :     // calculate the b64 length needed
     266           31 :     size_t b64LenNeeded = BASE64_ENCODED_LEN(rdn.size());
     267              : 
     268              :     // Ensure string has enough capacity for base64 encoded data
     269           31 :     outRDNBase64String.resize(b64LenNeeded);
     270              : 
     271           31 :     uint16_t encodedLen = Base64Encode(rdn.data(), static_cast<uint16_t>(rdn.size()), &outRDNBase64String[0]);
     272           31 :     outRDNBase64String.resize(encodedLen);
     273              : 
     274           31 :     return CHIP_NO_ERROR;
     275              : }
     276              : 
     277           24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer, std::string & outIssuerNameBase64String)
     278              : {
     279           24 :     return GetRDNBase64Str(certDer, outIssuerNameBase64String, RDNType::kIssuer);
     280              : }
     281              : 
     282            8 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectNameBase64Str(const ByteSpan & certDer,
     283              :                                                                   std::string & outSubjectNameBase64String)
     284              : {
     285            8 :     return GetRDNBase64Str(certDer, outSubjectNameBase64String, RDNType::kSubject);
     286              : }
     287              : 
     288              : // @param certDer Certificate, in DER format, to check for revocation
     289           24 : bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
     290              : {
     291           24 :     std::string serialNumber;
     292           24 :     std::string akid;
     293           24 :     std::string issuerName;
     294              : 
     295           24 :     VerifyOrReturnValue(CHIP_NO_ERROR == GetIssuerNameBase64Str(certDer, issuerName), false);
     296           24 :     ChipLogDetail(NotSpecified, "Issuer: %s", NullTerminated(issuerName.data(), issuerName.size()).c_str());
     297              : 
     298           24 :     VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false);
     299           24 :     ChipLogDetail(NotSpecified, "Serial Number: %s", NullTerminated(serialNumber.data(), serialNumber.size()).c_str());
     300              : 
     301           24 :     VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false);
     302           24 :     ChipLogDetail(NotSpecified, "AKID: %s", NullTerminated(akid.data(), akid.size()).c_str());
     303              : 
     304           24 :     return IsEntryInRevocationSet(akid, issuerName, serialNumber);
     305           24 : }
     306              : 
     307           14 : void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(
     308              :     const DeviceAttestationVerifier::AttestationInfo & info,
     309              :     Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion)
     310              : {
     311           14 :     AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
     312              : 
     313           14 :     if (mDeviceAttestationRevocationSetPath.empty() && mRevocationData.empty())
     314              :     {
     315            2 :         ChipLogProgress(NotSpecified, "WARNING: No revocation information available. Revocation checks will be skipped!");
     316            2 :         onCompletion->mCall(onCompletion->mContext, info, attestationError);
     317            2 :         return;
     318              :     }
     319              : 
     320           12 :     ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
     321              : 
     322           12 :     if (IsCertificateRevoked(info.dacDerBuffer))
     323              :     {
     324            3 :         ChipLogProgress(NotSpecified, "Found revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
     325            3 :         attestationError = AttestationVerificationResult::kDacRevoked;
     326              :     }
     327              : 
     328           12 :     ChipLogDetail(NotSpecified, "Checking for revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
     329              : 
     330           12 :     if (IsCertificateRevoked(info.paiDerBuffer))
     331              :     {
     332            2 :         ChipLogProgress(NotSpecified, "Found revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
     333              : 
     334            2 :         if (attestationError == AttestationVerificationResult::kDacRevoked)
     335              :         {
     336            1 :             attestationError = AttestationVerificationResult::kPaiAndDacRevoked;
     337              :         }
     338              :         else
     339              :         {
     340            1 :             attestationError = AttestationVerificationResult::kPaiRevoked;
     341              :         }
     342              :     }
     343              : 
     344           12 :     onCompletion->mCall(onCompletion->mContext, info, attestationError);
     345              : }
     346              : 
     347              : } // namespace Credentials
     348              : } // namespace chip
        

Generated by: LCOV version 2.0-1