Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dac_revocation: Perform cross validation against crl signer or crl signer delegator #35144

Merged
merged 9 commits into from
Nov 27, 2024
175 changes: 156 additions & 19 deletions src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <lib/support/BytesToHex.h>
#include <lib/support/logging/CHIPLogging.h>

#include <algorithm>
#include <fstream>
#include <json/json.h>

Expand All @@ -30,13 +31,52 @@ namespace chip {
namespace Credentials {

namespace {

static constexpr uint32_t kMaxIssuerBase64Len = BASE64_ENCODED_LEN(kMaxCertificateDistinguishedNameLength);

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

CHIP_ERROR X509_PemToDer(const std::string & pemCert, MutableByteSpan & derCert)
{
std::string beginMarker = "-----BEGIN CERTIFICATE-----";
std::string endMarker = "-----END CERTIFICATE-----";

std::size_t beginPos = pemCert.find(beginMarker);
VerifyOrReturnError(beginPos != std::string::npos, CHIP_ERROR_INVALID_ARGUMENT);

std::size_t endPos = pemCert.find(endMarker);
VerifyOrReturnError(endPos != std::string::npos, CHIP_ERROR_INVALID_ARGUMENT);

VerifyOrReturnError(beginPos < endPos, CHIP_ERROR_INVALID_ARGUMENT);

// Extract content between markers
std::string plainB64Str = pemCert.substr(beginPos + beginMarker.length(), endPos - (beginPos + beginMarker.length()));

// Remove all newline characters '\n' and '\r'
plainB64Str.erase(std::remove(plainB64Str.begin(), plainB64Str.end(), '\n'), plainB64Str.end());
plainB64Str.erase(std::remove(plainB64Str.begin(), plainB64Str.end(), '\r'), plainB64Str.end());

VerifyOrReturnError(!plainB64Str.empty(), CHIP_ERROR_INVALID_ARGUMENT);

// Verify we have enough room to store the decoded certificate
size_t maxDecodeLen = BASE64_MAX_DECODED_LEN(plainB64Str.size());
VerifyOrReturnError(derCert.size() >= maxDecodeLen, CHIP_ERROR_BUFFER_TOO_SMALL);

// decode b64
uint16_t derLen = Base64Decode(plainB64Str.c_str(), static_cast<uint16_t>(plainB64Str.size()), derCert.data());
VerifyOrReturnError(derLen != UINT16_MAX, CHIP_ERROR_INVALID_ARGUMENT);

derCert.reduce_size(derLen);

return CHIP_NO_ERROR;
}

} // anonymous namespace

CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationSetPath(std::string_view path)
Expand All @@ -52,6 +92,58 @@ void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
mDeviceAttestationRevocationSetPath = mDeviceAttestationRevocationSetPath.substr(0, 0);
}

// outSubject is subject encoded as base64 string
// outKeyId is SKID encoded as hex string
CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectAndKeyIdFromPEMCert(const std::string & certPEM, std::string & outSubject,
std::string & outKeyId)
{
// buffers and spans for storing crl signer delegator OR crl signer cert info
char subjectBuf[kMaxIssuerBase64Len] = { 0 };
char skidBuf[2 * kAuthorityKeyIdentifierLength] = { 0 };
uint8_t certDerBuf[kMax_x509_Certificate_Length] = { 0 };

MutableCharSpan subject(subjectBuf);
MutableCharSpan keyId(skidBuf);
MutableByteSpan certDER(certDerBuf);

ReturnLogErrorOnFailure(X509_PemToDer(certPEM, certDER));
ReturnErrorOnFailure(GetSubjectNameBase64Str(certDER, subject));
ReturnErrorOnFailure(GetSKIDHexStr(certDER, keyId));

outSubject = std::string(subject.data(), subject.size());
outKeyId = std::string(keyId.data(), keyId.size());

return CHIP_NO_ERROR;
}

// 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 certPEM;
[[maybe_unused]] std::string certType;

if (revokedSet.isMember("crl_signer_delegator"))
{
certPEM = revokedSet["crl_signer_delegator"].asString();
certType = "CRL Signer delegator";
}
else
{
certPEM = revokedSet["crl_signer_cert"].asString();
certType = "CRL Signer";
}

std::string subject; // crl signer or crl signer delegator subject
std::string keyId; // crl signer or crl signer delegator SKID
VerifyOrReturnValue(CHIP_NO_ERROR == GetSubjectAndKeyIdFromPEMCert(certPEM, subject, 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
// [
// {
Expand All @@ -62,6 +154,8 @@ void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
// "serial1 bytes as base64",
// "serial2 bytes as base64"
// ]
// "crl_signer_cert": "<PEM incoded CRL signer certificate>",
// "crl_signer_delegator": <PEM incoded CRL signer delegator certificate>,
// }
// ]
//
Expand Down Expand Up @@ -95,6 +189,7 @@ bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const CharSpan & akid
std::string serialNumber = std::string(serialNumberHexStr.data(), serialNumberHexStr.size());
std::string akid = std::string(akidHexStr.data(), akidHexStr.size());

// 6.2.4.2. Determining Revocation Status of an Entity
for (const auto & revokedSet : jsonData)
{
if (revokedSet["issuer_name"].asString() != issuerName)
Expand All @@ -105,6 +200,12 @@ bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const CharSpan & akid
{
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, akid, issuerName), false);

// 4.c check if serial number is revoked
for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
{
if (revokedSerialNumber.asString() == serialNumber)
Expand All @@ -116,14 +217,33 @@ bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const CharSpan & akid
return false;
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outAKIDHexStr)
CHIP_ERROR TestDACRevocationDelegateImpl::GetKeyIDHexStr(const ByteSpan & certDer, MutableCharSpan & outKeyIDHexStr, bool isAKID)
{
uint8_t akidBuf[kAuthorityKeyIdentifierLength];
MutableByteSpan akid(akidBuf);
static_assert(kAuthorityKeyIdentifierLength == kSubjectKeyIdentifierLength, "AKID and SKID length mismatch");

uint8_t keyIdBuf[kAuthorityKeyIdentifierLength];
MutableByteSpan keyId(keyIdBuf);

ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, akid));
if (isAKID)
{
ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, keyId));
}
else
{
ReturnErrorOnFailure(ExtractSKIDFromX509Cert(certDer, keyId));
}

return BytesToHexStr(akid, outAKIDHexStr);
return BytesToHexStr(keyId, outKeyIDHexStr);
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outAKIDHexStr)
{
return GetKeyIDHexStr(certDer, outAKIDHexStr, true);
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetSKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outSKIDHexStr)
{
return GetKeyIDHexStr(certDer, outSKIDHexStr, false /* isAKID */);
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, MutableCharSpan & outSerialNumberHexStr)
Expand All @@ -135,25 +255,44 @@ CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan &
return BytesToHexStr(serialNumber, outSerialNumberHexStr);
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer,
MutableCharSpan & outIssuerNameBase64String)
CHIP_ERROR TestDACRevocationDelegateImpl::GetRDNBase64Str(const ByteSpan & certDer, MutableCharSpan & outRDNBase64String,
bool isIssuer)
{
uint8_t issuerBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
MutableByteSpan issuer(issuerBuf);
uint8_t rdnBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
MutableByteSpan rdn(rdnBuf);

ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, issuer));
VerifyOrReturnError(outIssuerNameBase64String.size() >= BASE64_ENCODED_LEN(issuer.size()), CHIP_ERROR_BUFFER_TOO_SMALL);
if (isIssuer)
{
ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, rdn));
}
else
{
ReturnErrorOnFailure(ExtractSubjectFromX509Cert(certDer, rdn));
}

uint16_t encodedLen = Base64Encode(issuer.data(), static_cast<uint16_t>(issuer.size()), outIssuerNameBase64String.data());
outIssuerNameBase64String.reduce_size(encodedLen);
VerifyOrReturnError(outRDNBase64String.size() >= BASE64_ENCODED_LEN(rdn.size()), CHIP_ERROR_BUFFER_TOO_SMALL);

uint16_t encodedLen = Base64Encode(rdn.data(), static_cast<uint16_t>(rdn.size()), outRDNBase64String.data());
outRDNBase64String.reduce_size(encodedLen);
return CHIP_NO_ERROR;
}

bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer,
MutableCharSpan & outIssuerNameBase64String)
{
static constexpr uint32_t maxIssuerBase64Len = BASE64_ENCODED_LEN(kMaxCertificateDistinguishedNameLength);
return GetRDNBase64Str(certDer, outIssuerNameBase64String, true /* isIssuer */);
}

CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectNameBase64Str(const ByteSpan & certDer,
MutableCharSpan & outSubjectNameBase64String)
{
return GetRDNBase64Str(certDer, outSubjectNameBase64String, false /* isIssuer */);
}

char issuerNameBuffer[maxIssuerBase64Len] = { 0 };
// @param certDer Certificate, in DER format, to check for revocation
bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
{
char issuerNameBuffer[kMaxIssuerBase64Len] = { 0 };
char serialNumberHexStrBuffer[2 * kMaxCertificateSerialNumberLength] = { 0 };
char akidHexStrBuffer[2 * kAuthorityKeyIdentifierLength] = { 0 };

Expand All @@ -170,8 +309,6 @@ bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDe
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);
}

Expand All @@ -183,8 +320,8 @@ void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(

if (mDeviceAttestationRevocationSetPath.empty())
{

onCompletion->mCall(onCompletion->mContext, info, attestationError);
return;
}

ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma once

#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
#include <json/json.h>
#include <lib/support/Span.h>

#include <string>
Expand Down Expand Up @@ -52,11 +53,23 @@ class TestDACRevocationDelegateImpl : public DeviceAttestationRevocationDelegate
void ClearDeviceAttestationRevocationSetPath();

private:
bool CrossValidateCert(const Json::Value & revokedSet, const std::string & akIdHexStr, const std::string & issuerNameBase64Str);

CHIP_ERROR GetKeyIDHexStr(const ByteSpan & certDer, MutableCharSpan & outKeyIDHexStr, bool isAKID);
CHIP_ERROR GetAKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outAKIDHexStr);
CHIP_ERROR GetSKIDHexStr(const ByteSpan & certDer, MutableCharSpan & outSKIDHexStr);

CHIP_ERROR GetSerialNumberHexStr(const ByteSpan & certDer, MutableCharSpan & outSerialNumberHexStr);

CHIP_ERROR GetRDNBase64Str(const ByteSpan & certDer, MutableCharSpan & outRDNBase64String, bool isIssuer);
CHIP_ERROR GetIssuerNameBase64Str(const ByteSpan & certDer, MutableCharSpan & outIssuerNameBase64String);
CHIP_ERROR GetSubjectNameBase64Str(const ByteSpan & certDer, MutableCharSpan & outSubjectNameBase64String);

CHIP_ERROR GetSubjectAndKeyIdFromPEMCert(const std::string & certPEM, std::string & outSubject, std::string & outKeyId);

bool IsEntryInRevocationSet(const CharSpan & akidHexStr, const CharSpan & issuerNameBase64Str,
const CharSpan & serialNumberHexStr);

bool IsCertificateRevoked(const ByteSpan & certDer);

std::string mDeviceAttestationRevocationSetPath;
Expand Down
Loading
Loading