blob: 4b5b3c0b6b5e7d69f64bb30a7bb11669c81abfd1 [file] [log] [blame]
/*
*
* Copyright (c) 2021-2022 Project CHIP Authors
*
* 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 <credentials/CHIPCert.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/Linux/NetworkCommissioningDriver.h>
#include <limits>
#include <string>
#include <vector>
using namespace chip;
using namespace chip::Crypto;
using namespace chip::Credentials;
namespace chip {
namespace DeviceLayer {
namespace NetworkCommissioning {
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
// TODO(#14172): Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides
// some bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related
// code in ConnectivityManagerImpl).
namespace {
constexpr char kWiFiSSIDKeyName[] = "wifi-ssid";
constexpr char kWiFiCredentialsKeyName[] = "wifi-pass";
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
constexpr char kWifiNetworkIdentityKeyName[] = "wifi-ni";
constexpr char kWifiClientIdentityKeyName[] = "wifi-ci";
constexpr char kWifiClientIdentityKeypairKeyName[] = "wifi-cik";
inline CHIP_ERROR IgnoreNotFound(CHIP_ERROR err)
{
return (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : err;
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
} // namespace
// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to
// mStagingNetwork and persist the changes.
// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config.
// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then
// wpa_supplicant will manage the networks for us.)
CHIP_ERROR LinuxWiFiDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback)
{
CHIP_ERROR err;
WiFiNetwork network;
size_t valueLen = 0;
auto & kvs = PersistedStorage::KeyValueStoreMgr();
SuccessOrExit(err = IgnoreNotFound(kvs.Get(kWiFiSSIDKeyName, network.ssid, sizeof(network.ssid), &valueLen)));
if (valueLen != 0)
{
network.ssidLen = valueLen;
err = kvs.Get(kWiFiCredentialsKeyName, network.credentials, sizeof(network.credentials), &valueLen);
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
SuccessOrExit(
err = kvs.Get(kWifiNetworkIdentityKeyName, network.networkIdentity, sizeof(network.networkIdentity), &valueLen));
VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED);
network.networkIdentityLen = valueLen;
SuccessOrExit(
err = kvs.Get(kWifiClientIdentityKeyName, network.clientIdentity, sizeof(network.clientIdentity), &valueLen));
VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED);
network.clientIdentityLen = valueLen;
P256SerializedKeypair serializedKeypair;
SuccessOrExit(err = kvs.Get(kWifiClientIdentityKeypairKeyName, serializedKeypair.Bytes(), serializedKeypair.Capacity(),
&valueLen));
serializedKeypair.SetLength(valueLen);
network.clientIdentityKeypair = Platform::MakeShared<P256Keypair>();
SuccessOrExit(err = network.clientIdentityKeypair->Deserialize(serializedKeypair));
}
else
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
{
SuccessOrExit(err);
network.credentialsLen = valueLen;
}
mStagingNetwork = mSavedNetwork = network;
}
ConnectivityMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback);
return CHIP_NO_ERROR;
exit:
ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: Failed to load network configuration: %" CHIP_ERROR_FORMAT,
err.Format());
return err;
}
void LinuxWiFiDriver::Shutdown()
{
ConnectivityMgrImpl().SetNetworkStatusChangeCallback(nullptr);
}
CHIP_ERROR LinuxWiFiDriver::CommitConfiguration()
{
auto & kvs = PersistedStorage::KeyValueStoreMgr();
ReturnErrorOnFailure(kvs.Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen));
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
if (mStagingNetwork.UsingPDC())
{
ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWiFiCredentialsKeyName)));
ReturnErrorOnFailure(
kvs.Put(kWifiNetworkIdentityKeyName, mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen));
ReturnErrorOnFailure(
kvs.Put(kWifiClientIdentityKeyName, mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen));
P256SerializedKeypair serializedKeypair;
ReturnErrorOnFailure(mStagingNetwork.clientIdentityKeypair->Serialize(serializedKeypair));
ReturnErrorOnFailure(
kvs.Put(kWifiClientIdentityKeypairKeyName, serializedKeypair.ConstBytes(), serializedKeypair.Length()));
}
else
{
ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiNetworkIdentityKeyName)));
ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeyName)));
ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeypairKeyName)));
#else // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
{
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
ReturnErrorOnFailure(kvs.Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials, mStagingNetwork.credentialsLen));
}
ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig());
mSavedNetwork = mStagingNetwork;
return CHIP_NO_ERROR;
}
CHIP_ERROR LinuxWiFiDriver::RevertConfiguration()
{
mStagingNetwork = mSavedNetwork;
return CHIP_NO_ERROR;
}
Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText,
uint8_t & outNetworkIndex)
{
outDebugText.reduce_size(0);
outNetworkIndex = 0;
VerifyOrReturnError(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), Status::kBoundsExceeded);
// Do the check before setting the values, so the data is not updated on error.
VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange);
VerifyOrReturnError(!ssid.empty() && ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange);
memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size());
mStagingNetwork.credentialsLen = static_cast<decltype(mStagingNetwork.credentialsLen)>(credentials.size());
memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size());
mStagingNetwork.ssidLen = static_cast<decltype(mStagingNetwork.ssidLen)>(ssid.size());
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
mStagingNetwork.networkIdentityLen = 0;
mStagingNetwork.clientIdentityLen = 0;
mStagingNetwork.clientIdentityKeypair.reset();
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
return Status::kSuccess;
}
Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex)
{
outDebugText.reduce_size(0);
outNetworkIndex = 0;
VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound);
// Use empty ssid for representing invalid network
mStagingNetwork.ssidLen = 0;
return Status::kSuccess;
}
Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText)
{
outDebugText.reduce_size(0);
VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound);
VerifyOrReturnError(index == 0, Status::kOutOfRange);
// We only support one network, so reorder is actually no-op.
return Status::kSuccess;
}
void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Status networkingStatus = Status::kSuccess;
const auto & network = mStagingNetwork;
VerifyOrExit(network.Matches(networkId), networkingStatus = Status::kNetworkIDNotFound);
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
if (network.UsingPDC())
{
ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork (PDC) '%.*s'", network.ssidLen, network.ssid);
err = ConnectivityMgrImpl().ConnectWiFiNetworkWithPDCAsync(
ByteSpan(network.ssid, network.ssidLen), ByteSpan(network.networkIdentity, network.networkIdentityLen),
ByteSpan(network.clientIdentity, network.clientIdentityLen), *network.clientIdentityKeypair, callback);
}
else
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
{
ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork '%.*s'", network.ssidLen, network.ssid);
err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(network.ssid, network.ssidLen),
ByteSpan(network.credentials, network.credentialsLen), callback);
}
exit:
if (err != CHIP_NO_ERROR)
{
networkingStatus = Status::kUnknownError;
}
if (networkingStatus != Status::kSuccess)
{
ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %" CHIP_ERROR_FORMAT, err.Format());
callback->OnResult(networkingStatus, CharSpan(), 0);
}
}
void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
{
CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback);
if (err != CHIP_NO_ERROR)
{
callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
}
}
size_t LinuxWiFiDriver::WiFiNetworkIterator::Count()
{
return driver->mStagingNetwork.Empty() ? 0 : 1;
}
bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item)
{
if (exhausted || driver->mStagingNetwork.Empty())
{
return false;
}
memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen);
item.networkIDLen = driver->mStagingNetwork.ssidLen;
item.connected = false;
exhausted = true;
Network configuredNetwork;
CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConfiguredNetwork(configuredNetwork);
if (err == CHIP_NO_ERROR)
{
if (DeviceLayer::ConnectivityMgrImpl().IsWiFiStationConnected() && configuredNetwork.networkIDLen == item.networkIDLen &&
memcmp(configuredNetwork.networkID, item.networkID, item.networkIDLen) == 0)
{
item.connected = true;
}
}
return true;
}
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
CHIP_ERROR LinuxWiFiDriver::AddOrUpdateNetworkWithPDC(ByteSpan ssid, ByteSpan networkIdentity,
Optional<uint8_t> clientIdentityNetworkIndex, Status & outStatus,
MutableCharSpan & outDebugText, MutableByteSpan & outClientIdentity,
uint8_t & outNetworkIndex)
{
CHIP_ERROR err = CHIP_NO_ERROR;
outStatus = Status::kUnknownError;
VerifyOrExit(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), outStatus = Status::kBoundsExceeded);
VerifyOrExit(!ssid.empty() && ssid.size() <= sizeof(WiFiNetwork::ssid), outStatus = Status::kOutOfRange);
VerifyOrExit(!networkIdentity.empty() && networkIdentity.size() <= sizeof(WiFiNetwork::networkIdentity),
outStatus = Status::kOutOfRange);
VerifyOrExit(!clientIdentityNetworkIndex.HasValue() || (clientIdentityNetworkIndex.Value() == 0 && mStagingNetwork.UsingPDC()),
outStatus = Status::kOutOfRange);
{
WiFiNetwork network = mStagingNetwork; // update a copy first in case of errors
memcpy(network.ssid, ssid.data(), network.ssidLen = ssid.size());
memcpy(network.networkIdentity, networkIdentity.data(), network.networkIdentityLen = networkIdentity.size());
// If an existing client identity is being reused, we would need to copy it here,
// but since we're only supporting a single network we simply don't overwrite it.
if (!clientIdentityNetworkIndex.HasValue())
{
network.clientIdentityKeypair = Platform::MakeShared<P256Keypair>();
SuccessOrExit(err = network.clientIdentityKeypair->Initialize(ECPKeyTarget::ECDSA));
MutableByteSpan clientIdentity(network.clientIdentity);
SuccessOrExit(err = NewChipNetworkIdentity(*network.clientIdentityKeypair, clientIdentity));
network.clientIdentityLen = clientIdentity.size();
}
network.credentialsLen = 0;
SuccessOrExit(err = CopySpanToMutableSpan(ByteSpan(network.clientIdentity, network.clientIdentityLen), outClientIdentity));
mStagingNetwork = network;
outNetworkIndex = 0;
outStatus = Status::kSuccess;
}
exit:
outDebugText.reduce_size(0);
return err;
}
CHIP_ERROR LinuxWiFiDriver::GetNetworkIdentity(uint8_t networkIndex, MutableByteSpan & outNetworkIdentity)
{
VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT);
return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen), outNetworkIdentity);
}
CHIP_ERROR LinuxWiFiDriver::GetClientIdentity(uint8_t networkIndex, MutableByteSpan & outClientIdentity)
{
VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT);
return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen), outClientIdentity);
}
CHIP_ERROR LinuxWiFiDriver::SignWithClientIdentity(uint8_t networkIndex, const ByteSpan & message,
P256ECDSASignature & outSignature)
{
VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT);
return mStagingNetwork.clientIdentityKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC
#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
} // namespace NetworkCommissioning
} // namespace DeviceLayer
} // namespace chip