Skip to content

Commit

Permalink
Introduce initial TC-RR-1.1 (#22032)
Browse files Browse the repository at this point in the history
* Introduce initial TC-RR-1.1

- TC-RR-1.1 is a critical test to validate multi-fabric
  behavior is stable and actually works. The test, broadly,
  validates most of the minimas of the core elements of the spec,
  including ACL entries, certificate sizes, number of CASE
  sessions and subscriptions, number of paths, etc.

Issue #21736

- This PR introduces the core test and all associated minor
  changes to infrastructure to make it work.
- Still TODO:
  - More extensive cert size maximization (closer to 400 TLV bytes)
  - Add controller and commissionee CAT tags (test is 95% equivalent
    to test plan, but a couple ACL fields differ because of this, in
    ways that don't detract from proving what needs proving
  - Validation that local/peer session IDs have not changed. This is
    not technically needed with the SDK as-is based on the methodology
    but it would future-proof the test against some future optimizations
    that may change subscription behavior in a way that the test would
    not validate CASE sessions remain.
  - Clean-up more after the test, so that a factory reset before/after
    is not needed.

Testing done:
- Passes on Linux against all-clusters, all-clusters-minimal and
  lighting app, with both minimal mdns and Avahi.
- Passes on some other platforms (not named here)

To run within SDK (from scratch: the build steps can be skipped thereafter):

- In one terminal:
  - Build chip-lighting-app linux
  - `clear && rm -f kvs1 && out/debug/standalone/chip-lighting-app --discriminator 1234 --KVS kvs1 --trace_decode 1`

- In another terminal:
  - Build
    - `rm -rf out/python*`
    - `scripts/build_python.sh -m platform -i separate`
  - Run
    - `source ./out/python_env/bin/activate`
    - `python3 src/python_testing/TC_RR_1_1.py --commissioning-method on-network --long-discriminator 1234 --passcode 20202021`
      - Add `--bool-arg skip_user_label_cluster_steps:true` to the end of the command line
        if your DUT has broken UserLabel clusters (but if you have those, fix them :)

* More work towards CAT tags

* Address review comments

* Fixed CAT tag testing

* Update src/controller/python/chip/utils/CommissioningBuildingBlocks.py

Co-authored-by: Jerry Johns <johnsj@google.com>
  • Loading branch information
tcarmelveilleux and mrjerryjohns authored Aug 19, 2022
1 parent 932bf7e commit dfc1a70
Show file tree
Hide file tree
Showing 13 changed files with 744 additions and 49 deletions.
6 changes: 6 additions & 0 deletions examples/chip-tool/commands/common/CHIPCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(std::string key, chip::FabricId f
// store the credentials in persistent storage, and
// generate when not available in the storage.
ReturnLogErrorOnFailure(mCommissionerStorage.Init(key.c_str()));
if (mUseMaxSizedCerts.HasValue())
{
auto option = CredentialIssuerCommands::CredentialIssuerOptions::kMaximizeCertificateSizes;
mCredIssuerCmds->SetCredentialIssuerOption(option, mUseMaxSizedCerts.Value());
}

ReturnLogErrorOnFailure(mCredIssuerCmds->InitializeCredentialsIssuer(mCommissionerStorage));

chip::MutableByteSpan nocSpan(noc.Get(), chip::Controller::kMaxCHIPDERCertLength);
Expand Down
4 changes: 4 additions & 0 deletions examples/chip-tool/commands/common/CHIPCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class CHIPCommand : public Command
"4. The default if not specified is \"alpha\".");
AddArgument("commissioner-nodeid", 0, UINT64_MAX, &mCommissionerNodeId,
"The node id to use for chip-tool. If not provided, kTestControllerNodeId (112233, 0x1B669) will be used.");
AddArgument("use-max-sized-certs", 0, 1, &mUseMaxSizedCerts,
"Maximize the size of operational certificates. If not provided or 0 (\"false\"), normally sized operational "
"certificates are generated.");
#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
AddArgument("trace_file", &mTraceFile);
AddArgument("trace_log", 0, 1, &mTraceLog);
Expand Down Expand Up @@ -153,6 +156,7 @@ class CHIPCommand : public Command
chip::Optional<chip::NodeId> mCommissionerNodeId;
chip::Optional<uint16_t> mBleAdapterId;
chip::Optional<char *> mPaaTrustStorePath;
chip::Optional<bool> mUseMaxSizedCerts;

// Cached trust store so commands other than the original startup command
// can spin up commissioners as needed.
Expand Down
19 changes: 19 additions & 0 deletions examples/chip-tool/commands/common/CredentialIssuerCommands.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,23 @@ class CredentialIssuerCommands
virtual CHIP_ERROR GenerateControllerNOCChain(chip::NodeId nodeId, chip::FabricId fabricId, const chip::CATValues & cats,
chip::Crypto::P256Keypair & keypair, chip::MutableByteSpan & rcac,
chip::MutableByteSpan & icac, chip::MutableByteSpan & noc) = 0;

// All options must start false
enum CredentialIssuerOptions : uint8_t
{
kMaximizeCertificateSizes = 0, // If set, certificate chains will be maximized for testing via padding
};

virtual void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled)
{
// Do nothing
(void) option;
(void) isEnabled;
}

virtual bool GetCredentialIssuerOption(CredentialIssuerOptions option)
{
// All options always start false
return false;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,33 @@ class ExampleCredentialIssuerCommands : public CredentialIssuerCommands
return mOpCredsIssuer.GenerateNOCChainAfterValidation(nodeId, fabricId, cats, keypair.Pubkey(), rcac, icac, noc);
}

void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled) override
{
switch (option)
{
case CredentialIssuerOptions::kMaximizeCertificateSizes:
mUsesMaxSizedCerts = isEnabled;
mOpCredsIssuer.SetMaximallyLargeCertsUsed(mUsesMaxSizedCerts);
break;
default:
break;
}
}

bool GetCredentialIssuerOption(CredentialIssuerOptions option) override
{
switch (option)
{
case CredentialIssuerOptions::kMaximizeCertificateSizes:
return mUsesMaxSizedCerts;
default:
return false;
}
}

protected:
bool mUsesMaxSizedCerts = false;

private:
chip::Controller::ExampleOperationalCredentialsIssuer mOpCredsIssuer;
};
168 changes: 154 additions & 14 deletions src/controller/ExampleOperationalCredentialsIssuer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,127 @@ using namespace Credentials;
using namespace Crypto;
using namespace TLV;

namespace {

enum CertType : uint8_t
{
kRcac = 0,
kIcac = 1,
kNoc = 2
};

CHIP_ERROR IssueX509Cert(uint32_t now, uint32_t validity, ChipDN issuerDn, ChipDN desiredDn, CertType certType, bool maximizeSize,
const Crypto::P256PublicKey & subjectPublicKey, Crypto::P256Keypair & issuerKeypair,
MutableByteSpan & outX509Cert)
{
constexpr size_t kDERCertDnEncodingOverhead = 11;
constexpr size_t kTLVCertDnEncodingOverhead = 3;
constexpr size_t kMaxCertPaddingLength = 150;
constexpr size_t kTLVDesiredSize = kMaxCHIPCertLength - 50;

Platform::ScopedMemoryBuffer<uint8_t> derBuf;
ReturnErrorCodeIf(!derBuf.Alloc(kMaxDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan derSpan{ derBuf.Get(), kMaxDERCertLength };

int64_t serialNumber = 1;

switch (certType)
{
case CertType::kRcac: {
X509CertRequestParams rcacRequest = { serialNumber, now, now + validity, desiredDn, desiredDn };
ReturnErrorOnFailure(NewRootX509Cert(rcacRequest, issuerKeypair, derSpan));
break;
}
case CertType::kIcac: {
X509CertRequestParams icacRequest = { serialNumber, now, now + validity, desiredDn, issuerDn };
ReturnErrorOnFailure(NewICAX509Cert(icacRequest, subjectPublicKey, issuerKeypair, derSpan));
break;
}
case CertType::kNoc: {
X509CertRequestParams nocRequest = { serialNumber, now, now + validity, desiredDn, issuerDn };
ReturnErrorOnFailure(NewNodeOperationalX509Cert(nocRequest, subjectPublicKey, issuerKeypair, derSpan));
break;
}
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}

if (maximizeSize && (desiredDn.RDNCount() < CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES))
{
Platform::ScopedMemoryBuffer<uint8_t> paddedTlvBuf;
ReturnErrorCodeIf(!paddedTlvBuf.Alloc(kMaxCHIPCertLength + kMaxCertPaddingLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan paddedTlvSpan{ paddedTlvBuf.Get(), kMaxCHIPCertLength + kMaxCertPaddingLength };
ReturnErrorOnFailure(ConvertX509CertToChipCert(derSpan, paddedTlvSpan));

Platform::ScopedMemoryBuffer<uint8_t> paddedDerBuf;
ReturnErrorCodeIf(!paddedDerBuf.Alloc(kMaxDERCertLength + kMaxCertPaddingLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan paddedDerSpan{ paddedDerBuf.Get(), kMaxDERCertLength + kMaxCertPaddingLength };

Platform::ScopedMemoryBuffer<char> fillerBuf;
ReturnErrorCodeIf(!fillerBuf.Alloc(kMaxCertPaddingLength), CHIP_ERROR_NO_MEMORY);
memset(fillerBuf.Get(), 'A', kMaxCertPaddingLength);

int derPaddingLen = static_cast<int>(kMaxDERCertLength - kDERCertDnEncodingOverhead - derSpan.size());
int tlvPaddingLen = static_cast<int>(kTLVDesiredSize - kTLVCertDnEncodingOverhead - paddedTlvSpan.size());
if (certType == CertType::kRcac)
{
// For RCAC the issuer/subject DN are the same so padding will be present in both
derPaddingLen = (derPaddingLen - static_cast<int>(kDERCertDnEncodingOverhead)) / 2;
tlvPaddingLen = (tlvPaddingLen - static_cast<int>(kTLVCertDnEncodingOverhead)) / 2;
}

size_t paddingLen = 0;
if (derPaddingLen >= 1 && tlvPaddingLen >= 1)
{
paddingLen = std::min(static_cast<size_t>(std::min(derPaddingLen, tlvPaddingLen)), kMaxCertPaddingLength);
}

for (; paddingLen > 0; paddingLen--)
{
paddedDerSpan = MutableByteSpan{ paddedDerBuf.Get(), kMaxDERCertLength + kMaxCertPaddingLength };
paddedTlvSpan = MutableByteSpan{ paddedTlvBuf.Get(), kMaxCHIPCertLength + kMaxCertPaddingLength };

ChipDN certDn = desiredDn;
// Fill the padding in the DomainNameQualifier DN
certDn.AddAttribute_DNQualifier(CharSpan(fillerBuf.Get(), paddingLen), false);

switch (certType)
{
case CertType::kRcac: {
X509CertRequestParams rcacRequest = { serialNumber, now, now + validity, certDn, certDn };
ReturnErrorOnFailure(NewRootX509Cert(rcacRequest, issuerKeypair, paddedDerSpan));
break;
}
case CertType::kIcac: {
X509CertRequestParams icacRequest = { serialNumber, now, now + validity, certDn, issuerDn };
ReturnErrorOnFailure(NewICAX509Cert(icacRequest, subjectPublicKey, issuerKeypair, paddedDerSpan));
break;
}
case CertType::kNoc: {
X509CertRequestParams nocRequest = { serialNumber, now, now + validity, certDn, issuerDn };
ReturnErrorOnFailure(NewNodeOperationalX509Cert(nocRequest, subjectPublicKey, issuerKeypair, paddedDerSpan));
break;
}
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}

ReturnErrorOnFailure(ConvertX509CertToChipCert(paddedDerSpan, paddedTlvSpan));

ChipLogProgress(Controller, "Generated maximized certificate with %u DER bytes, %u TLV bytes",
static_cast<unsigned>(paddedDerSpan.size()), static_cast<unsigned>(paddedTlvSpan.size()));
if (paddedDerSpan.size() <= kMaxDERCertLength && paddedTlvSpan.size() <= kMaxCHIPCertLength)
{
return CopySpanToMutableSpan(paddedDerSpan, outX509Cert);
}
}
}

return CopySpanToMutableSpan(derSpan, outX509Cert);
}

} // namespace

CHIP_ERROR ExampleOperationalCredentialsIssuer::Initialize(PersistentStorageDelegate & storage)
{
using namespace ASN1;
Expand Down Expand Up @@ -122,6 +243,12 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
uint16_t rcacBufLen = static_cast<uint16_t>(std::min(rcac.size(), static_cast<size_t>(UINT16_MAX)));
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen));
// Always regenerate RCAC on maximally sized certs. The keys remain the same, so everything is fine.
if (mUseMaximallySizedCerts)
{
err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}

if (err == CHIP_NO_ERROR)
{
uint64_t rcacId;
Expand All @@ -137,10 +264,14 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(mIssuerId));

ChipLogProgress(Controller, "Generating RCAC");
X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn };
ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac));

ReturnErrorOnFailure(IssueX509Cert(mNow, mValidity, rcac_dn, rcac_dn, CertType::kRcac, mUseMaximallySizedCerts,
mIssuer.Pubkey(), mIssuer, rcac));
VerifyOrReturnError(CanCastTo<uint16_t>(rcac.size()), CHIP_ERROR_INTERNAL);

// Re-extract DN based on final generated cert
rcac_dn = ChipDN{};
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn));

PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast<uint16_t>(rcac.size()))));
}
Expand All @@ -149,6 +280,11 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
uint16_t icacBufLen = static_cast<uint16_t>(std::min(icac.size(), static_cast<size_t>(UINT16_MAX)));
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key,
err = mStorage->SyncGetKeyValue(key, icac.data(), icacBufLen));
// Always regenerate ICAC on maximally sized certs. The keys remain the same, so everything is fine.
if (mUseMaximallySizedCerts)
{
err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
if (err == CHIP_NO_ERROR)
{
uint64_t icacId;
Expand All @@ -164,10 +300,14 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterICACId(mIntermediateIssuerId));

ChipLogProgress(Controller, "Generating ICAC");
X509CertRequestParams icac_request = { 0, mNow, mNow + mValidity, icac_dn, rcac_dn };
ReturnErrorOnFailure(NewICAX509Cert(icac_request, mIntermediateIssuer.Pubkey(), mIssuer, icac));

ReturnErrorOnFailure(IssueX509Cert(mNow, mValidity, rcac_dn, icac_dn, CertType::kIcac, mUseMaximallySizedCerts,
mIntermediateIssuer.Pubkey(), mIssuer, icac));
VerifyOrReturnError(CanCastTo<uint16_t>(icac.size()), CHIP_ERROR_INTERNAL);

// Re-extract DN based on final generated cert
icac_dn = ChipDN{};
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(icac, icac_dn));

PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key,
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, icac.data(), static_cast<uint16_t>(icac.size()))));
}
Expand All @@ -178,8 +318,8 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
ReturnErrorOnFailure(noc_dn.AddCATs(cats));

ChipLogProgress(Controller, "Generating NOC");
X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn };
return NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc);
return IssueX509Cert(mNow, mValidity, icac_dn, noc_dn, CertType::kNoc, mUseMaximallySizedCerts, pubkey, mIntermediateIssuer,
noc);
}

CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce,
Expand Down Expand Up @@ -227,16 +367,16 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan
ReturnErrorOnFailure(VerifyCertificateSigningRequest(csr.data(), csr.size(), pubkey));

chip::Platform::ScopedMemoryBuffer<uint8_t> noc;
ReturnErrorCodeIf(!noc.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan nocSpan(noc.Get(), kMaxCHIPDERCertLength);
ReturnErrorCodeIf(!noc.Alloc(kMaxDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan nocSpan(noc.Get(), kMaxDERCertLength);

chip::Platform::ScopedMemoryBuffer<uint8_t> icac;
ReturnErrorCodeIf(!icac.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan icacSpan(icac.Get(), kMaxCHIPDERCertLength);
ReturnErrorCodeIf(!icac.Alloc(kMaxDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan icacSpan(icac.Get(), kMaxDERCertLength);

chip::Platform::ScopedMemoryBuffer<uint8_t> rcac;
ReturnErrorCodeIf(!rcac.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan rcacSpan(rcac.Get(), kMaxCHIPDERCertLength);
ReturnErrorCodeIf(!rcac.Alloc(kMaxDERCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan rcacSpan(rcac.Get(), kMaxDERCertLength);

ReturnErrorOnFailure(
GenerateNOCChainAfterValidation(assignedId, mNextFabricId, chip::kUndefinedCATs, pubkey, rcacSpan, icacSpan, nocSpan));
Expand Down
7 changes: 5 additions & 2 deletions src/controller/ExampleOperationalCredentialsIssuer.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class DLL_EXPORT ExampleOperationalCredentialsIssuer : public OperationalCredent
mNodeIdRequested = true;
}

void SetMaximallyLargeCertsUsed(bool areMaximallyLargeCertsUsed) { mUseMaximallySizedCerts = areMaximallyLargeCertsUsed; }

void SetFabricIdForNextNOCRequest(FabricId fabricId) override { mNextFabricId = fabricId; }

/**
Expand Down Expand Up @@ -108,15 +110,16 @@ class DLL_EXPORT ExampleOperationalCredentialsIssuer : public OperationalCredent
Crypto::P256Keypair mIssuer;
Crypto::P256Keypair mIntermediateIssuer;
bool mInitialized = false;
uint32_t mIssuerId = 0;
uint32_t mIntermediateIssuerId = 1;
uint32_t mIssuerId = 1;
uint32_t mIntermediateIssuerId = 2;
uint32_t mNow = 0;

// By default, let's set validity to 10 years
uint32_t mValidity = 365 * 24 * 60 * 60 * 10;

NodeId mNextAvailableNodeId = 1;
PersistentStorageDelegate * mStorage = nullptr;
bool mUseMaximallySizedCerts = false;

NodeId mNextRequestedNodeId = 1;
FabricId mNextFabricId = 1;
Expand Down
16 changes: 14 additions & 2 deletions src/controller/python/OpCredsBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class OperationalCredentialsAdapter : public OperationalCredentialsDelegate
return mExampleOpCredsIssuer.GenerateNOCChainAfterValidation(nodeId, fabricId, cats, pubKey, rcac, icac, noc);
}

void SetMaximallyLargeCertsUsed(bool enabled) { mExampleOpCredsIssuer.SetMaximallyLargeCertsUsed(enabled); }

private:
CHIP_ERROR GenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, const ByteSpan & attestationSignature,
const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI,
Expand Down Expand Up @@ -360,9 +362,10 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex

CATValues catValues;

if ((caseAuthTagLen + 1) > kMaxSubjectCATAttributeCount)
if (caseAuthTagLen > kMaxSubjectCATAttributeCount)
{
ChipLogError(Controller, "# of CASE Tags exceeds kMaxSubjectCATAttributeCount");
ChipLogError(Controller, "Too many of CASE Tags (%u) exceeds kMaxSubjectCATAttributeCount",
static_cast<unsigned>(caseAuthTagLen));
return CHIP_ERROR_INVALID_ARGUMENT.AsInteger();
}

Expand Down Expand Up @@ -414,6 +417,15 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex
return CHIP_NO_ERROR.AsInteger();
}

ChipError::StorageType pychip_OpCreds_SetMaximallyLargeCertsUsed(OpCredsContext * context, bool enabled)
{
VerifyOrReturnError(context != nullptr && context->mAdapter != nullptr, CHIP_ERROR_INCORRECT_STATE.AsInteger());

context->mAdapter->SetMaximallyLargeCertsUsed(enabled);

return CHIP_NO_ERROR.AsInteger();
}

void pychip_OpCreds_FreeDelegate(OpCredsContext * context)
{
Platform::Delete(context);
Expand Down
Loading

0 comments on commit dfc1a70

Please sign in to comment.