Skip to content

Commit

Permalink
Separate device attestation and operational cert provisioning into se…
Browse files Browse the repository at this point in the history
…parate steps (#12915)

* DAC and PAI storage/control to AutoCommissioner.

* Move attestation step to commissioning delegate

* Move signing request and NOC chain gen to AutoCommissioner.

* Remaining steps pulled out into external commissioner.

* Fix up error handling

* Add some additional logging

I can't repro the cirque failure locally for some reason. Adding
logging to figure out what's up.

* I'm dumb.

* Error checking on parameter setting.

* Fix sizing on thread data set

* Use Variant instead of union.

* Simplify error handling.

* Get rid of some debug cruft.

* cleanup operational ID extraction from certificates

This amendment takes advantage #12915, which makes available
both the root certificate and NOC in memory at the same time
during commissioning.  Because of this, the commissioner no
longer needs to extract and store the Root CA public key to
generate the compressed fabric ID.

Co-authored-by: Michael Sandstedt <michael.sandstedt@gmail.com>
  • Loading branch information
cecille and msandstedt authored Jan 25, 2022
1 parent bb2838f commit 09a4921
Show file tree
Hide file tree
Showing 11 changed files with 570 additions and 469 deletions.
2 changes: 2 additions & 0 deletions src/app/DeviceProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class DLL_EXPORT DeviceProxy

virtual bool IsActive() const { return true; }

virtual CHIP_ERROR SetPeerId(ByteSpan rcac, ByteSpan noc) { return CHIP_ERROR_NOT_IMPLEMENTED; }

const ReliableMessageProtocolConfig & GetMRPConfig() const { return mMRPConfig; }

protected:
Expand Down
225 changes: 217 additions & 8 deletions src/controller/AutoCommissioner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,23 @@
#include <controller/AutoCommissioner.h>

#include <controller/CHIPDeviceController.h>
#include <credentials/CHIPCert.h>
#include <lib/support/SafeInt.h>

namespace chip {
namespace Controller {

AutoCommissioner::~AutoCommissioner()
{
ReleaseDAC();
ReleasePAI();
}

void AutoCommissioner::SetOperationalCredentialsDelegate(OperationalCredentialsDelegate * operationalCredentialsDelegate)
{
mOperationalCredentialsDelegate = operationalCredentialsDelegate;
}

CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParameters & params)
{
mParams = params;
Expand All @@ -31,9 +44,11 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam
ByteSpan dataset = params.GetThreadOperationalDataset().Value();
if (dataset.size() > CommissioningParameters::kMaxThreadDatasetLen)
{
ChipLogError(Controller, "Thread operational data set is too large");
return CHIP_ERROR_INVALID_ARGUMENT;
}
memcpy(mThreadOperationalDataset, dataset.data(), dataset.size());
ChipLogProgress(Controller, "Setting thread operational dataset from parameters");
mParams.SetThreadOperationalDataset(ByteSpan(mThreadOperationalDataset, dataset.size()));
}
if (params.HasWiFiCredentials())
Expand All @@ -42,13 +57,42 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam
if (creds.ssid.size() > CommissioningParameters::kMaxSsidLen ||
creds.credentials.size() > CommissioningParameters::kMaxCredentialsLen)
{
ChipLogError(Controller, "Wifi credentials are too large");
return CHIP_ERROR_INVALID_ARGUMENT;
}
memcpy(mSsid, creds.ssid.data(), creds.ssid.size());
memcpy(mCredentials, creds.credentials.data(), creds.credentials.size());
ChipLogProgress(Controller, "Setting wifi credentials from parameters");
mParams.SetWiFiCredentials(
WiFiCredentials(ByteSpan(mSsid, creds.ssid.size()), ByteSpan(mCredentials, creds.credentials.size())));
}
// If the AttestationNonce is passed in, using that else using a random one..
if (params.HasAttestationNonce())
{
ChipLogProgress(Controller, "Setting attestation nonce from parameters");
VerifyOrReturnError(params.GetAttestationNonce().Value().size() == sizeof(mAttestationNonce), CHIP_ERROR_INVALID_ARGUMENT);
memcpy(mAttestationNonce, params.GetAttestationNonce().Value().data(), params.GetAttestationNonce().Value().size());
}
else
{
ChipLogProgress(Controller, "Setting attestation nonce to random value");
Crypto::DRBG_get_bytes(mAttestationNonce, sizeof(mAttestationNonce));
}
mParams.SetAttestationNonce(ByteSpan(mAttestationNonce, sizeof(mAttestationNonce)));

if (params.HasCSRNonce())
{
ChipLogProgress(Controller, "Setting CSR nonce from parameters");
VerifyOrReturnError(params.GetCSRNonce().Value().size() == sizeof(mCSRNonce), CHIP_ERROR_INVALID_ARGUMENT);
memcpy(mCSRNonce, params.GetCSRNonce().Value().data(), params.GetCSRNonce().Value().size());
}
else
{
ChipLogProgress(Controller, "Setting CSR nonce to random value");
Crypto::DRBG_get_bytes(mCSRNonce, sizeof(mCSRNonce));
}
mParams.SetCSRNonce(ByteSpan(mCSRNonce, sizeof(mCSRNonce)));

return CHIP_NO_ERROR;
}

Expand All @@ -65,8 +109,22 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStag
case CommissioningStage::kArmFailsafe:
return CommissioningStage::kConfigRegulatory;
case CommissioningStage::kConfigRegulatory:
return CommissioningStage::kDeviceAttestation;
case CommissioningStage::kDeviceAttestation:
return CommissioningStage::kSendPAICertificateRequest;
case CommissioningStage::kSendPAICertificateRequest:
return CommissioningStage::kSendDACCertificateRequest;
case CommissioningStage::kSendDACCertificateRequest:
return CommissioningStage::kSendAttestationRequest;
case CommissioningStage::kSendAttestationRequest:
return CommissioningStage::kAttestationVerification;
case CommissioningStage::kAttestationVerification:
return CommissioningStage::kSendOpCertSigningRequest;
case CommissioningStage::kSendOpCertSigningRequest:
return CommissioningStage::kGenerateNOCChain;
case CommissioningStage::kGenerateNOCChain:
return CommissioningStage::kSendTrustedRootCert;
case CommissioningStage::kSendTrustedRootCert:
return CommissioningStage::kSendNOC;
case CommissioningStage::kSendNOC:
// TODO(cecille): device attestation casues operational cert provisioinging to happen, This should be a separate stage.
// For thread and wifi, this should go to network setup then enable. For on-network we can skip right to finding the
// operational network because the provisioning of certificates will trigger the device to start operational advertising.
Expand Down Expand Up @@ -123,7 +181,6 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStag

// Currently unimplemented.
case CommissioningStage::kConfigACL:
case CommissioningStage::kCheckCertificates:
return CommissioningStage::kError;
// Neither of these have a next stage so return kError;
case CommissioningStage::kCleanup:
Expand Down Expand Up @@ -154,14 +211,91 @@ Optional<System::Clock::Timeout> AutoCommissioner::GetCommandTimeout(Commissioni
}
}

void AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report)
CHIP_ERROR AutoCommissioner::NOCChainGenerated(ByteSpan noc, ByteSpan icac, ByteSpan rcac)
{
// Reuse ICA Cert buffer for temporary store Root Cert.
MutableByteSpan rootCert = MutableByteSpan(mICACertBuffer);
ReturnErrorOnFailure(Credentials::ConvertX509CertToChipCert(rcac, rootCert));
mParams.SetRootCert(rootCert);

MutableByteSpan noCert = MutableByteSpan(mNOCertBuffer);
ReturnErrorOnFailure(Credentials::ConvertX509CertToChipCert(noc, noCert));
mParams.SetNoc(noCert);

CommissioningStage nextStage = CommissioningStage::kSendTrustedRootCert;
mCommissioner->PerformCommissioningStep(mCommissioneeDeviceProxy, nextStage, mParams, this, 0, GetCommandTimeout(nextStage));

// Trusted root cert has been sent, so we can re-use the icac buffer for the icac.
if (!icac.empty())
{
MutableByteSpan icaCert = MutableByteSpan(mICACertBuffer);
ReturnErrorOnFailure(Credentials::ConvertX509CertToChipCert(icac, icaCert));
mParams.SetIcac(icaCert);
}
else
{
mParams.SetIcac(ByteSpan());
}

return CHIP_NO_ERROR;
}

CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report)
{
if (report.stageCompleted == CommissioningStage::kFindOperational)
if (err != CHIP_NO_ERROR)
{
mOperationalDeviceProxy = report.OperationalNodeFoundData.operationalProxy;
ChipLogError(Controller, "Failed to perform commissioning step %d", static_cast<int>(report.stageCompleted));
return err;
}
switch (report.stageCompleted)
{
case CommissioningStage::kSendPAICertificateRequest:
SetPAI(report.Get<RequestedCertificate>().certificate);
break;
case CommissioningStage::kSendDACCertificateRequest:
SetDAC(report.Get<RequestedCertificate>().certificate);
break;
case CommissioningStage::kSendAttestationRequest:
// These don't need to be deep copied to local memory because they are used in this one step then never again.
mParams.SetAttestationElements(report.Get<AttestationResponse>().attestationElements)
.SetAttestationSignature(report.Get<AttestationResponse>().signature);
// TODO: Does this need to be done at runtime? Seems like this could be done earlier and we woouldn't need to hold a
// reference to the operational credential delegate here
if (mOperationalCredentialsDelegate != nullptr)
{
MutableByteSpan nonce(mCSRNonce);
ReturnErrorOnFailure(mOperationalCredentialsDelegate->ObtainCsrNonce(nonce));
mParams.SetCSRNonce(ByteSpan(mCSRNonce, sizeof(mCSRNonce)));
}
break;
case CommissioningStage::kSendOpCertSigningRequest: {
NOCChainGenerationParameters nocParams;
nocParams.nocsrElements = report.Get<AttestationResponse>().attestationElements;
nocParams.signature = report.Get<AttestationResponse>().signature;
mParams.SetNOCChainGenerationParameters(nocParams);
}
break;
case CommissioningStage::kGenerateNOCChain:
// For NOC chain generation, we re-use the buffers. NOCChainGenerated triggers the next stage before
// storing the returned certs, so just return here without triggering the next stage.
return NOCChainGenerated(report.Get<NocChain>().noc, report.Get<NocChain>().icac, report.Get<NocChain>().rcac);
case CommissioningStage::kFindOperational:
mOperationalDeviceProxy = report.Get<OperationalNodeFoundData>().operationalProxy;
break;
case CommissioningStage::kCleanup:
ReleasePAI();
ReleaseDAC();
mCommissioneeDeviceProxy = nullptr;
mOperationalDeviceProxy = nullptr;
mParams = CommissioningParameters();
return CHIP_NO_ERROR;
default:
break;
}

CommissioningStage nextStage = GetNextCommissioningStage(report.stageCompleted, err);
DeviceProxy * proxy = mCommissioneeDeviceProxy;

DeviceProxy * proxy = mCommissioneeDeviceProxy;
if (nextStage == CommissioningStage::kSendComplete ||
(nextStage == CommissioningStage::kCleanup && mOperationalDeviceProxy != nullptr))
{
Expand All @@ -171,12 +305,87 @@ void AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, CommissioningDe
if (proxy == nullptr)
{
ChipLogError(Controller, "Invalid device for commissioning");
return;
return CHIP_ERROR_INCORRECT_STATE;
}

mParams.SetCompletionStatus(err);
// TODO: Get real endpoint
mCommissioner->PerformCommissioningStep(proxy, nextStage, mParams, this, 0, GetCommandTimeout(nextStage));
return CHIP_NO_ERROR;
}

void AutoCommissioner::ReleaseDAC()
{
if (mDAC != nullptr)
{
Platform::MemoryFree(mDAC);
}
mDACLen = 0;
mDAC = nullptr;
}

CHIP_ERROR AutoCommissioner::SetDAC(const ByteSpan & dac)
{
if (dac.size() == 0)
{
ReleaseDAC();
return CHIP_NO_ERROR;
}

VerifyOrReturnError(dac.size() <= Credentials::kMaxDERCertLength, CHIP_ERROR_INVALID_ARGUMENT);
if (mDACLen != 0)
{
ReleaseDAC();
}

VerifyOrReturnError(CanCastTo<uint16_t>(dac.size()), CHIP_ERROR_INVALID_ARGUMENT);
if (mDAC == nullptr)
{
mDAC = static_cast<uint8_t *>(chip::Platform::MemoryAlloc(dac.size()));
}
VerifyOrReturnError(mDAC != nullptr, CHIP_ERROR_NO_MEMORY);
mDACLen = static_cast<uint16_t>(dac.size());
memcpy(mDAC, dac.data(), mDACLen);
mParams.SetDAC(ByteSpan(mDAC, mDACLen));

return CHIP_NO_ERROR;
}

void AutoCommissioner::ReleasePAI()
{
if (mPAI != nullptr)
{
chip::Platform::MemoryFree(mPAI);
}
mPAILen = 0;
mPAI = nullptr;
}

CHIP_ERROR AutoCommissioner::SetPAI(const chip::ByteSpan & pai)
{
if (pai.size() == 0)
{
ReleasePAI();
return CHIP_NO_ERROR;
}

VerifyOrReturnError(pai.size() <= Credentials::kMaxDERCertLength, CHIP_ERROR_INVALID_ARGUMENT);
if (mPAILen != 0)
{
ReleasePAI();
}

VerifyOrReturnError(CanCastTo<uint16_t>(pai.size()), CHIP_ERROR_INVALID_ARGUMENT);
if (mPAI == nullptr)
{
mPAI = static_cast<uint8_t *>(chip::Platform::MemoryAlloc(pai.size()));
}
VerifyOrReturnError(mPAI != nullptr, CHIP_ERROR_NO_MEMORY);
mPAILen = static_cast<uint16_t>(pai.size());
memcpy(mPAI, pai.data(), mPAILen);
mParams.SetPAI(ByteSpan(mPAI, mPAILen));

return CHIP_NO_ERROR;
}

} // namespace Controller
Expand Down
33 changes: 29 additions & 4 deletions src/controller/AutoCommissioner.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,49 @@ class AutoCommissioner : public CommissioningDelegate
{
public:
AutoCommissioner(DeviceCommissioner * commissioner) : mCommissioner(commissioner) {}
~AutoCommissioner();
CHIP_ERROR SetCommissioningParameters(const CommissioningParameters & params);
void SetOperationalCredentialsDelegate(OperationalCredentialsDelegate * operationalCredentialsDelegate);

void StartCommissioning(CommissioneeDeviceProxy * proxy);

void CommissioningStepFinished(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report) override;
// Delegate functions
CHIP_ERROR CommissioningStepFinished(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report) override;

private:
CommissioningStage GetNextCommissioningStage(CommissioningStage currentStage, CHIP_ERROR lastErr);
void ReleaseDAC();
void ReleasePAI();

CHIP_ERROR SetDAC(const ByteSpan & dac);
CHIP_ERROR SetPAI(const ByteSpan & pai);

ByteSpan GetDAC() const { return ByteSpan(mDAC, mDACLen); }
ByteSpan GetPAI() const { return ByteSpan(mPAI, mPAILen); }

CHIP_ERROR NOCChainGenerated(ByteSpan noc, ByteSpan icac, ByteSpan rcac);
Optional<System::Clock::Timeout> GetCommandTimeout(CommissioningStage stage);

DeviceCommissioner * mCommissioner;
CommissioneeDeviceProxy * mCommissioneeDeviceProxy = nullptr;
OperationalDeviceProxy * mOperationalDeviceProxy = nullptr;
CommissioningParameters mParams = CommissioningParameters();
CommissioneeDeviceProxy * mCommissioneeDeviceProxy = nullptr;
OperationalDeviceProxy * mOperationalDeviceProxy = nullptr;
OperationalCredentialsDelegate * mOperationalCredentialsDelegate = nullptr;
CommissioningParameters mParams = CommissioningParameters();
// Memory space for the commisisoning parameters that come in as ByteSpans - the caller is not guaranteed to retain this memory
// TODO(cecille): Include memory from CommissioneeDeviceProxy once BLE is moved over
uint8_t mSsid[CommissioningParameters::kMaxSsidLen];
uint8_t mCredentials[CommissioningParameters::kMaxCredentialsLen];
uint8_t mThreadOperationalDataset[CommissioningParameters::kMaxThreadDatasetLen];

// TODO: Why were the nonces statically allocated, but the certs dynamically allocated?
uint8_t * mDAC = nullptr;
uint16_t mDACLen = 0;
uint8_t * mPAI = nullptr;
uint16_t mPAILen = 0;
uint8_t mAttestationNonce[kAttestationNonceLength];
uint8_t mCSRNonce[kOpCSRNonceLength];
uint8_t mNOCertBuffer[Credentials::kMaxCHIPCertLength];
uint8_t mICACertBuffer[Credentials::kMaxCHIPCertLength];
};

} // namespace Controller
Expand Down
Loading

0 comments on commit 09a4921

Please sign in to comment.