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

Separate device attestation and operational cert provisioning into separate steps #12915

Merged
merged 19 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(const Crypto::P256PublicKey & rootPublicKey, 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