blob: d11036e74984ff33fe05b8cbed1194ebe7edbd18 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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;
if (params.HasThreadOperationalDataset())
{
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())
{
WiFiCredentials creds = params.GetWiFiCredentials().Value();
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;
}
CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStage currentStage, CHIP_ERROR lastErr)
{
if (lastErr != CHIP_NO_ERROR)
{
return CommissioningStage::kCleanup;
}
switch (currentStage)
{
case CommissioningStage::kSecurePairing:
return CommissioningStage::kArmFailsafe;
case CommissioningStage::kArmFailsafe:
return CommissioningStage::kConfigRegulatory;
case CommissioningStage::kConfigRegulatory:
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.
if (mParams.HasWiFiCredentials())
{
return CommissioningStage::kWiFiNetworkSetup;
}
else if (mParams.HasThreadOperationalDataset())
{
return CommissioningStage::kThreadNetworkSetup;
}
else
{
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
return CommissioningStage::kFindOperational;
#else
return CommissioningStage::kSendComplete;
#endif
}
case CommissioningStage::kWiFiNetworkSetup:
if (mParams.HasThreadOperationalDataset())
{
return CommissioningStage::kThreadNetworkSetup;
}
else
{
return CommissioningStage::kWiFiNetworkEnable;
}
case CommissioningStage::kThreadNetworkSetup:
if (mParams.HasWiFiCredentials())
{
return CommissioningStage::kWiFiNetworkEnable;
}
else
{
return CommissioningStage::kThreadNetworkEnable;
}
case CommissioningStage::kWiFiNetworkEnable:
if (mParams.HasThreadOperationalDataset())
{
return CommissioningStage::kThreadNetworkEnable;
}
else
{
return CommissioningStage::kFindOperational;
}
case CommissioningStage::kThreadNetworkEnable:
return CommissioningStage::kFindOperational;
case CommissioningStage::kFindOperational:
return CommissioningStage::kSendComplete;
case CommissioningStage::kSendComplete:
return CommissioningStage::kCleanup;
// Currently unimplemented.
case CommissioningStage::kConfigACL:
return CommissioningStage::kError;
// Neither of these have a next stage so return kError;
case CommissioningStage::kCleanup:
case CommissioningStage::kError:
return CommissioningStage::kError;
}
return CommissioningStage::kError;
}
void AutoCommissioner::StartCommissioning(CommissioneeDeviceProxy * proxy)
{
// TODO: check that there is no commissioning in progress currently.
mCommissioneeDeviceProxy = proxy;
mCommissioner->PerformCommissioningStep(mCommissioneeDeviceProxy, CommissioningStage::kArmFailsafe, mParams, this, 0,
GetCommandTimeout(CommissioningStage::kArmFailsafe));
}
Optional<System::Clock::Timeout> AutoCommissioner::GetCommandTimeout(CommissioningStage stage)
{
switch (stage)
{
case CommissioningStage::kWiFiNetworkEnable:
case CommissioningStage::kThreadNetworkEnable:
return MakeOptional(System::Clock::Timeout(System::Clock::Seconds16(30)));
default:
// Use default timeout specified in the IM.
return NullOptional;
}
}
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 (err != CHIP_NO_ERROR)
{
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;
if (nextStage == CommissioningStage::kSendComplete ||
(nextStage == CommissioningStage::kCleanup && mOperationalDeviceProxy != nullptr))
{
proxy = mOperationalDeviceProxy;
}
if (proxy == nullptr)
{
ChipLogError(Controller, "Invalid device for commissioning");
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
} // namespace chip