Skip to content

Commit

Permalink
dac_revocation: Perform cross validation against crl signer or crl si…
Browse files Browse the repository at this point in the history
…gner delegator (project-chip#35144)

* dac_revocation: Support crl signer and crl signer delegator

- Add crl signer and crl signer delegator cert in the python script
  which generates the revocation set
- perform the cross validation of DAC/PAI with crl signer and crl signer
  delegator cert
- Extended and added unit tests for crl signer delegator case

* include algoright and remove the local var declaration

* use unused attribute

* Fix the buffer overrun and use maybe_unused instead of unused attribute

* we do not need to differentiate pai/dac when cross verifying

* add checks on return value and unit tests on malformed crl signer cert

* use std::string instead of const char *

* address some more reviews

* Update src/credentials/attestation_verifier/TestDACRevocationDelegateImpl.cpp

Co-authored-by: Boris Zbarsky <[email protected]>

---------

Co-authored-by: Boris Zbarsky <[email protected]>
  • Loading branch information
shubhamdp and bzbarsky-apple authored Nov 27, 2024
1 parent ffbc362 commit 63182c4
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 23 deletions.
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

0 comments on commit 63182c4

Please sign in to comment.