blob: 620d28c34431490d0846801c8f46269fd39262d7 [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()
{
SetCommissioningParameters(CommissioningParameters());
}
AutoCommissioner::~AutoCommissioner()
{
ReleaseDAC();
ReleasePAI();
}
void AutoCommissioner::SetOperationalCredentialsDelegate(OperationalCredentialsDelegate * operationalCredentialsDelegate)
{
mOperationalCredentialsDelegate = operationalCredentialsDelegate;
}
CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParameters & params)
{
mParams = params;
if (params.GetThreadOperationalDataset().HasValue())
{
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.GetWiFiCredentials().HasValue())
{
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 (params.GetCountryCode().HasValue())
{
auto & code = params.GetCountryCode().Value();
MutableCharSpan copiedCode(mCountryCode);
if (CopyCharSpanToMutableCharSpan(code, copiedCode) == CHIP_NO_ERROR)
{
mParams.SetCountryCode(copiedCode);
}
else
{
ChipLogError(Controller, "Country code is too large: %u", static_cast<unsigned>(code.size()));
}
}
// If the AttestationNonce is passed in, using that else using a random one..
if (params.GetAttestationNonce().HasValue())
{
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.GetCSRNonce().HasValue())
{
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;
}
const CommissioningParameters & AutoCommissioner::GetCommissioningParameters() const
{
return mParams;
}
CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStage currentStage, CHIP_ERROR & lastErr)
{
auto nextStage = GetNextCommissioningStageInternal(currentStage, lastErr);
if (lastErr == CHIP_NO_ERROR)
{
ChipLogProgress(Controller, "Commissioning stage next step: '%s' -> '%s'", StageToString(currentStage),
StageToString(nextStage));
}
else
{
ChipLogProgress(Controller, "Going from commissioning step '%s' with lastErr = '%s' -> '%s'", StageToString(currentStage),
lastErr.AsString(), StageToString(nextStage));
}
return nextStage;
}
CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(CommissioningStage currentStage, CHIP_ERROR & lastErr)
{
if (lastErr != CHIP_NO_ERROR)
{
return CommissioningStage::kCleanup;
}
switch (currentStage)
{
case CommissioningStage::kSecurePairing:
return CommissioningStage::kReadCommissioningInfo;
case CommissioningStage::kReadCommissioningInfo:
if (mDeviceCommissioningInfo.general.breadcrumb > 0)
{
// If the breadcrumb is 0, the failsafe was disarmed.
// We failed on network setup or later, the node failsafe has not been re-armed and the breadcrumb has not been reset.
// Per the spec, we restart from after adding the NOC.
return GetNextCommissioningStage(CommissioningStage::kSendNOC, lastErr);
}
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::kValidateCSR;
case CommissioningStage::kValidateCSR:
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 (mNeedsNetworkSetup)
{
if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId)
{
return CommissioningStage::kWiFiNetworkSetup;
}
if (mParams.GetThreadOperationalDataset().HasValue() &&
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
{
return CommissioningStage::kThreadNetworkSetup;
}
ChipLogError(Controller, "Required network information not provided in commissioning parameters");
ChipLogError(Controller, "Parameters supplied: wifi (%s) thread (%s)",
mParams.GetWiFiCredentials().HasValue() ? "yes" : "no",
mParams.GetThreadOperationalDataset().HasValue() ? "yes" : "no");
ChipLogError(Controller, "Device supports: wifi (%s) thread(%s)",
mDeviceCommissioningInfo.network.wifi.endpoint == kInvalidEndpointId ? "no" : "yes",
mDeviceCommissioningInfo.network.thread.endpoint == kInvalidEndpointId ? "no" : "yes");
lastErr = CHIP_ERROR_INVALID_ARGUMENT;
return CommissioningStage::kCleanup;
}
else
{
return CommissioningStage::kFindOperational;
}
case CommissioningStage::kWiFiNetworkSetup:
if (mParams.GetThreadOperationalDataset().HasValue() &&
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
{
return CommissioningStage::kThreadNetworkSetup;
}
else
{
return CommissioningStage::kWiFiNetworkEnable;
}
case CommissioningStage::kThreadNetworkSetup:
if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId)
{
return CommissioningStage::kWiFiNetworkEnable;
}
else
{
return CommissioningStage::kThreadNetworkEnable;
}
case CommissioningStage::kWiFiNetworkEnable:
if (mParams.GetThreadOperationalDataset().HasValue() &&
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
{
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;
// Neither of these have a next stage so return kError;
case CommissioningStage::kCleanup:
case CommissioningStage::kError:
return CommissioningStage::kError;
}
return CommissioningStage::kError;
}
EndpointId AutoCommissioner::GetEndpoint(const CommissioningStage & stage) const
{
switch (stage)
{
case CommissioningStage::kWiFiNetworkSetup:
case CommissioningStage::kWiFiNetworkEnable:
return mDeviceCommissioningInfo.network.wifi.endpoint;
case CommissioningStage::kThreadNetworkSetup:
case CommissioningStage::kThreadNetworkEnable:
return mDeviceCommissioningInfo.network.thread.endpoint;
default:
return kRootEndpointId;
}
}
CHIP_ERROR AutoCommissioner::StartCommissioning(DeviceCommissioner * commissioner, CommissioneeDeviceProxy * proxy)
{
if (commissioner == nullptr)
{
ChipLogError(Controller, "Invalid DeviceCommissioner");
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (proxy == nullptr || !proxy->GetSecureSession().HasValue())
{
ChipLogError(Controller, "Device proxy secure session error");
return CHIP_ERROR_INVALID_ARGUMENT;
}
mCommissioner = commissioner;
mCommissioneeDeviceProxy = proxy;
mNeedsNetworkSetup =
mCommissioneeDeviceProxy->GetSecureSession().Value()->AsSecureSession()->GetPeerAddress().GetTransportType() ==
Transport::Type::kBle;
CHIP_ERROR err = CHIP_NO_ERROR;
CommissioningStage nextStage = GetNextCommissioningStage(CommissioningStage::kSecurePairing, err);
mCommissioner->PerformCommissioningStep(mCommissioneeDeviceProxy, nextStage, mParams, this, GetEndpoint(nextStage),
GetCommandTimeout(nextStage));
return CHIP_NO_ERROR;
}
Optional<System::Clock::Timeout> AutoCommissioner::GetCommandTimeout(CommissioningStage stage) const
{
// Per spec, all commands that are sent with the arm failsafe held need at least a 30s timeout.
// Network clusters can indicate the time required to connect, so if we are connecting, use that time as long as it is > 30s.
app::Clusters::NetworkCommissioning::Attributes::ConnectMaxTimeSeconds::TypeInfo::DecodableType seconds = 30;
switch (stage)
{
case CommissioningStage::kWiFiNetworkEnable:
ChipLogError(Controller, "Setting wifi connection time min = %u", mDeviceCommissioningInfo.network.wifi.minConnectionTime);
seconds = std::max(mDeviceCommissioningInfo.network.wifi.minConnectionTime, seconds);
break;
case CommissioningStage::kThreadNetworkEnable:
seconds = std::max(mDeviceCommissioningInfo.network.thread.minConnectionTime, seconds);
break;
default:
break;
}
return MakeOptional(System::Clock::Timeout(System::Clock::Seconds16(seconds)));
}
CHIP_ERROR AutoCommissioner::NOCChainGenerated(ByteSpan noc, ByteSpan icac, ByteSpan rcac, AesCcm128KeySpan ipk,
NodeId adminSubject)
{
// 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());
}
mParams.SetIpk(ipk);
mParams.SetAdminSubject(adminSubject);
return CHIP_NO_ERROR;
}
CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report)
{
CompletionStatus completionStatus;
completionStatus.err = err;
if (err == CHIP_NO_ERROR)
{
ChipLogProgress(Controller, "Successfully finished commissioning step '%s'", StageToString(report.stageCompleted));
}
else
{
ChipLogProgress(Controller, "Error on commissioning step '%s': '%s'", StageToString(report.stageCompleted), err.AsString());
}
if (err != CHIP_NO_ERROR)
{
completionStatus.failedStage = MakeOptional(report.stageCompleted);
ChipLogError(Controller, "Failed to perform commissioning step %d", static_cast<int>(report.stageCompleted));
if (report.Is<AttestationErrorInfo>())
{
completionStatus.attestationResult = MakeOptional(report.Get<AttestationErrorInfo>().attestationResult);
if ((report.Get<AttestationErrorInfo>().attestationResult ==
Credentials::AttestationVerificationResult::kDacProductIdMismatch) ||
(report.Get<AttestationErrorInfo>().attestationResult ==
Credentials::AttestationVerificationResult::kDacVendorIdMismatch))
{
ChipLogError(Controller,
"Failed device attestation. Device vendor and/or product ID do not match the IDs expected. "
"Verify DAC certificate chain and certification declaration to ensure spec rules followed.");
}
}
else if (report.Is<CommissioningErrorInfo>())
{
completionStatus.commissioningError = MakeOptional(report.Get<CommissioningErrorInfo>().commissioningError);
}
else if (report.Is<NetworkCommissioningStatusInfo>())
{
completionStatus.networkCommissioningStatus =
MakeOptional(report.Get<NetworkCommissioningStatusInfo>().networkCommissioningStatus);
}
}
else
{
switch (report.stageCompleted)
{
case CommissioningStage::kReadCommissioningInfo:
mDeviceCommissioningInfo = report.Get<ReadCommissioningInfo>();
if (!mParams.GetFailsafeTimerSeconds().HasValue() && mDeviceCommissioningInfo.general.recommendedFailsafe > 0)
{
mParams.SetFailsafeTimerSeconds(mDeviceCommissioningInfo.general.recommendedFailsafe);
}
mParams.SetRemoteVendorId(mDeviceCommissioningInfo.basic.vendorId)
.SetRemoteProductId(mDeviceCommissioningInfo.basic.productId)
.SetDefaultRegulatoryLocation(mDeviceCommissioningInfo.general.currentRegulatoryLocation)
.SetLocationCapability(mDeviceCommissioningInfo.general.locationCapability);
break;
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 wouldn'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<CSRResponse>().nocsrElements;
nocParams.signature = report.Get<CSRResponse>().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,
report.Get<NocChain>().ipk, report.Get<NocChain>().adminSubject);
case CommissioningStage::kFindOperational:
mOperationalDeviceProxy = report.Get<OperationalNodeFoundData>().operationalProxy;
break;
case CommissioningStage::kCleanup:
ReleasePAI();
ReleaseDAC();
mCommissioneeDeviceProxy = nullptr;
mOperationalDeviceProxy = nullptr;
mDeviceCommissioningInfo = ReadCommissioningInfo();
return CHIP_NO_ERROR;
default:
break;
}
}
CommissioningStage nextStage = GetNextCommissioningStage(report.stageCompleted, err);
if (nextStage == CommissioningStage::kError)
{
return CHIP_ERROR_INCORRECT_STATE;
}
// If GetNextCommissioningStage indicated a failure, don't lose track of
// that. But don't overwrite any existing failures we had hanging
// around.
if (completionStatus.err == CHIP_NO_ERROR)
{
completionStatus.err = 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(completionStatus);
mCommissioner->PerformCommissioningStep(proxy, nextStage, mParams, this, GetEndpoint(nextStage), 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