blob: 7a02d8aa93b4146de3d8a637ef19abe3edfcd228 [file]
/*
*
* 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 <algorithm>
#include <fstream>
#include <json/json.h>
using namespace chip::Crypto;
namespace chip {
namespace Credentials {
namespace {
CHIP_ERROR BytesToHexStr(const ByteSpan & bytes, std::string & outHexStr)
{
size_t hexLength = bytes.size() * 2;
outHexStr.resize(hexLength);
Encoding::HexFlags flags = Encoding::HexFlags::kUppercase;
return BytesToHex(bytes.data(), bytes.size(), &outHexStr[0], hexLength, flags);
}
} // anonymous namespace
CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationSetPath(std::string_view path)
{
VerifyOrReturnError(path.empty() != true, CHIP_ERROR_INVALID_ARGUMENT);
mDeviceAttestationRevocationSetPath = path;
return CHIP_NO_ERROR;
}
CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationData(const std::string & jsonData)
{
mRevocationData = jsonData;
return CHIP_NO_ERROR;
}
void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
{
// clear the string_view
mDeviceAttestationRevocationSetPath = mDeviceAttestationRevocationSetPath.substr(0, 0);
}
void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationData()
{
mRevocationData.clear();
}
// Check if issuer and AKID matches with the crl signer OR crl signer delegator's subject and SKID
bool TestDACRevocationDelegateImpl::CrossValidateCert(const Json::Value & revokedSet, const std::string & akidHexStr,
const std::string & issuerNameBase64Str)
{
std::string certBase64;
[[maybe_unused]] std::string certType;
if (revokedSet.isMember("crl_signer_delegator"))
{
certBase64 = revokedSet["crl_signer_delegator"].asString();
certType = "CRL Signer delegator";
}
else
{
certBase64 = revokedSet["crl_signer_cert"].asString();
certType = "CRL Signer";
}
uint8_t certDerBuf[kMax_x509_Certificate_Length] = { 0 };
MutableByteSpan certDER(certDerBuf);
// Verify we have enough room to store the decoded certificate
size_t maxDecodeLen = BASE64_MAX_DECODED_LEN(certBase64.size());
VerifyOrReturnValue(certDER.size() >= maxDecodeLen, false);
uint16_t derLen = Base64Decode(certBase64.c_str(), static_cast<uint16_t>(certBase64.size()), certDER.data());
VerifyOrReturnValue(derLen != UINT16_MAX, false);
certDER.reduce_size(derLen);
std::string subject;
std::string keyId;
VerifyOrReturnValue(CHIP_NO_ERROR == GetSubjectNameBase64Str(certDER, subject), false);
VerifyOrReturnValue(CHIP_NO_ERROR == GetSKIDHexStr(certDER, keyId), false);
ChipLogDetail(NotSpecified, "%s: Subject: %s", certType.c_str(), subject.c_str());
ChipLogDetail(NotSpecified, "%s: SKID: %s", certType.c_str(), keyId.c_str());
return (akidHexStr == keyId && issuerNameBase64Str == subject);
}
// 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"
// ]
// "crl_signer_cert": "<base64 encoded DER certificate>",
// "crl_signer_delegator": "<base64 encoded DER certificate>",
// }
// ]
//
bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const std::string & akidHexStr, const std::string & issuerNameBase64Str,
const std::string & serialNumberHexStr)
{
Json::Value jsonData;
// Try direct data first, then fall back to file
if (!mRevocationData.empty())
{
std::string errs;
std::istringstream jsonStream(!mRevocationData.empty() ? mRevocationData : "[]");
if (!Json::parseFromStream(Json::CharReaderBuilder(), jsonStream, &jsonData, &errs))
{
ChipLogError(NotSpecified, "Failed to parse JSON data: %s", errs.c_str());
return false;
}
}
else if (!mDeviceAttestationRevocationSetPath.empty())
{
std::string errs;
std::ifstream file(mDeviceAttestationRevocationSetPath.c_str());
if (!file.is_open())
{
ChipLogError(NotSpecified, "Failed to open file: %s", mDeviceAttestationRevocationSetPath.c_str());
return false;
}
bool parsingSuccessful = Json::parseFromStream(Json::CharReaderBuilder(), file, &jsonData, &errs);
file.close();
if (!parsingSuccessful)
{
ChipLogError(NotSpecified, "Failed to parse JSON from file: %s", errs.c_str());
return false;
}
}
else
{
ChipLogProgress(NotSpecified, "No revocation data available");
// No revocation data available
return false;
}
VerifyOrReturnValue(jsonData.isArray(), false, ChipLogError(NotSpecified, "Revocation set is not a valid JSON Array"));
// 6.2.4.2. Determining Revocation Status of an Entity
for (const auto & revokedSet : jsonData)
{
VerifyOrReturnValue(revokedSet.isObject(), false,
ChipLogError(NotSpecified, "Revocation set entry is not a valid JSON object"));
if (revokedSet["issuer_name"].asString() != issuerNameBase64Str)
{
continue;
}
if (revokedSet["issuer_subject_key_id"].asString() != akidHexStr)
{
continue;
}
// 4.a cross validate PAI with crl signer OR crl signer delegator
// 4.b cross validate DAC with crl signer OR crl signer delegator
VerifyOrReturnValue(CrossValidateCert(revokedSet, akidHexStr, issuerNameBase64Str), false);
// 4.c check if serial number is revoked
for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
{
if (revokedSerialNumber.asString() == serialNumberHexStr)
{
return true;
}
}
}
return false;
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetKeyIDHexStr(const ByteSpan & certDer, std::string & outKeyIDHexStr,
KeyIdType keyIdType)
{
static_assert(kAuthorityKeyIdentifierLength == kSubjectKeyIdentifierLength, "AKID and SKID length mismatch");
uint8_t keyIdBuf[kAuthorityKeyIdentifierLength];
MutableByteSpan keyId(keyIdBuf);
switch (keyIdType)
{
case KeyIdType::kAKID:
ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, keyId));
break;
case KeyIdType::kSKID:
ReturnErrorOnFailure(ExtractSKIDFromX509Cert(certDer, keyId));
break;
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
return BytesToHexStr(keyId, outKeyIDHexStr);
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, std::string & outAKIDHexStr)
{
return GetKeyIDHexStr(certDer, outAKIDHexStr, KeyIdType::kAKID);
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetSKIDHexStr(const ByteSpan & certDer, std::string & outSKIDHexStr)
{
return GetKeyIDHexStr(certDer, outSKIDHexStr, KeyIdType::kSKID);
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, std::string & outSerialNumberHexStr)
{
uint8_t serialNumberBuf[kMaxCertificateSerialNumberLength] = { 0 };
MutableByteSpan serialNumber(serialNumberBuf);
ReturnErrorOnFailure(ExtractSerialNumberFromX509Cert(certDer, serialNumber));
return BytesToHexStr(serialNumber, outSerialNumberHexStr);
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetRDNBase64Str(const ByteSpan & certDer, std::string & outRDNBase64String,
RDNType rdnType)
{
uint8_t rdnBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
MutableByteSpan rdn(rdnBuf);
switch (rdnType)
{
case RDNType::kIssuer:
ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, rdn));
break;
case RDNType::kSubject:
ReturnErrorOnFailure(ExtractSubjectFromX509Cert(certDer, rdn));
break;
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
// calculate the b64 length needed
size_t b64LenNeeded = BASE64_ENCODED_LEN(rdn.size());
// Ensure string has enough capacity for base64 encoded data
outRDNBase64String.resize(b64LenNeeded);
uint16_t encodedLen = Base64Encode(rdn.data(), static_cast<uint16_t>(rdn.size()), &outRDNBase64String[0]);
outRDNBase64String.resize(encodedLen);
return CHIP_NO_ERROR;
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer, std::string & outIssuerNameBase64String)
{
return GetRDNBase64Str(certDer, outIssuerNameBase64String, RDNType::kIssuer);
}
CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectNameBase64Str(const ByteSpan & certDer,
std::string & outSubjectNameBase64String)
{
return GetRDNBase64Str(certDer, outSubjectNameBase64String, RDNType::kSubject);
}
// @param certDer Certificate, in DER format, to check for revocation
bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
{
std::string serialNumber;
std::string akid;
std::string issuerName;
VerifyOrReturnValue(CHIP_NO_ERROR == GetIssuerNameBase64Str(certDer, issuerName), false);
ChipLogDetail(NotSpecified, "Issuer: %s", NullTerminated(issuerName.data(), issuerName.size()).c_str());
VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false);
ChipLogDetail(NotSpecified, "Serial Number: %s", NullTerminated(serialNumber.data(), serialNumber.size()).c_str());
VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false);
ChipLogDetail(NotSpecified, "AKID: %s", NullTerminated(akid.data(), akid.size()).c_str());
return IsEntryInRevocationSet(akid, issuerName, serialNumber);
}
void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(
const DeviceAttestationVerifier::AttestationInfo & info,
Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion)
{
AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
if (mDeviceAttestationRevocationSetPath.empty() && mRevocationData.empty())
{
ChipLogProgress(NotSpecified, "WARNING: No revocation information available. Revocation checks will be skipped!");
onCompletion->mCall(onCompletion->mContext, info, attestationError);
return;
}
ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
if (IsCertificateRevoked(info.dacDerBuffer))
{
ChipLogProgress(NotSpecified, "Found revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
attestationError = AttestationVerificationResult::kDacRevoked;
}
ChipLogDetail(NotSpecified, "Checking for revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
if (IsCertificateRevoked(info.paiDerBuffer))
{
ChipLogProgress(NotSpecified, "Found revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
if (attestationError == AttestationVerificationResult::kDacRevoked)
{
attestationError = AttestationVerificationResult::kPaiAndDacRevoked;
}
else
{
attestationError = AttestationVerificationResult::kPaiRevoked;
}
}
onCompletion->mCall(onCompletion->mContext, info, attestationError);
}
} // namespace Credentials
} // namespace chip