diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn index c907b0aaa83996..501a42718723fa 100644 --- a/src/credentials/BUILD.gn +++ b/src/credentials/BUILD.gn @@ -40,6 +40,9 @@ static_library("credentials") { "GroupDataProviderImpl.cpp", "LastKnownGoodTime.cpp", "LastKnownGoodTime.h", + "OperationalCertificateStore.h", + "PersistentStorageOpCertStore.cpp", + "PersistentStorageOpCertStore.h", "attestation_verifier/DeviceAttestationDelegate.h", "attestation_verifier/DeviceAttestationVerifier.cpp", "attestation_verifier/DeviceAttestationVerifier.h", diff --git a/src/credentials/OperationalCertificateStore.h b/src/credentials/OperationalCertificateStore.h new file mode 100644 index 00000000000000..4540da9d99621e --- /dev/null +++ b/src/credentials/OperationalCertificateStore.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2022 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. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace Credentials { + +class OperationalCertificateStore +{ +public: + enum class CertChainElement : uint8_t + { + kRcac = 0, + kIcac = 1, + kNoc = 2 + }; + + virtual ~OperationalCertificateStore() {} + + // ==== API designed for commisionables to support fail-safe (although can be used by controllers) ==== + + /** + * @brief Returns true if a pending root certificate exists and is active from a previous + * `AddNewTrustedRootCertForFabric`. + */ + virtual bool HasPendingRootCert() const = 0; + + /** + * @brief Returns true if a pending operational certificate chain exists and is active from a previous + * `AddNewOpCertsForFabric` or `UpdateOpCertsForFabric`. + */ + virtual bool HasPendingNocChain() const = 0; + + /** + * @brief Returns whether a usable operational certificates chain exists for the given fabric. + * + * Returns true even if the certificates are not persisted yet. Only returns true if a certificate + * is presently usable such that `GetCertificate` would succeed for the fabric. + * + * @param fabricIndex - FabricIndex for which availability of certificate will be checked. + * @param element - Element of the certificate chain whose presence needs to be checked + * @return true if there an active obtainable operational certificate of the given type for the given FabricIndex, + * false otherwise. + */ + virtual bool HasCertificateForFabric(FabricIndex fabricIndex, CertChainElement element) const = 0; + + /** + * @brief Add and temporarily activate a new Trusted Root Certificate to storage for the given fabric + * + * The certificate is temporary until committed or reverted. + * The certificate is committed to storage on `CommitOpCertsForFabric`. + * The certificate is destroyed if `RevertPendingOpCerts` is called before `CommitOpCertsForFabric`. + * + * Only one pending trusted root certificate is supported at a time and it is illegal + * to call this method if there is already a persisted root certificate for the given + * fabric. + * + * Uniqueness constraints for roots (see AddTrustedRootCertificate command in spec) is not + * enforced by this method and must be done as a more holistic check elsewhere. Cryptographic + * signature verification or path validation is not enforced by this method. + * + * If `UpdateOpCertsForFabric` had been called before this method, this method will return + * CHIP_ERROR_INCORRECT_STATE since it is illegal to update trusted roots when updating an + * existing NOC chain. + * + * @param fabricIndex - FabricIndex for which a new trusted root certificate should be added + * @param rcac - Buffer containing the root certificate to add. + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary root cert + * @retval CHIP_ERROR_INVALID_ARGUMENT if the certificate is empty or too large + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method + * is called after `UpdateOpCertsForFabric`, or if there was + * already a pending or persisted root certificate for the given `fabricIndex`. + * @retval other CHIP_ERROR value on internal errors + */ + virtual CHIP_ERROR AddNewTrustedRootCertForFabric(FabricIndex fabricIndex, const ByteSpan & rcac) = 0; + + /** + * @brief Add and temporarily activate an operational certificate chain for the given fabric. + * + * The certificate chain is temporary until committed or reverted. + * The certificate chain is committed to storage on `CommitOpCertsForFabric`. + * The certificate chain is destroyed if `RevertPendingOpCerts` is called before `CommitOpCertsForFabric`. + * + * Only one pending operational certificate chain is supported at a time and it is illegal + * to call this method if there is already a persisted certificate chain for the given + * fabric. + * + * Cryptographic signature verification or path validation is not enforced by this method. + * + * If `UpdateOpCertsForFabric` had been called before this method, this method will return + * CHIP_ERROR_INCORRECT_STATE since it is illegal to add a certificate chain after + * updating an existing NOC updating prior to a commit. + * + * If `AddNewTrustedRootCertForFabric` had not been called before this method, this method will + * return CHIP_ERROR_INCORRECT_STATE since it is illegal in this implementation to store an + * NOC chain without associated root. + * + * NOTE: The Matter spec allows AddNOC without AddTrustedRootCertificate if the NOC + * chains to an existing root, to support root reuse. In this implementation, we expect each + * fabric to store the root with the rest of the chain. Because of this, callers must ensure + * that if an AddNOC command is done and no trusted root was added, that the requisite existing + * root be "copied over" to match. + * + * @param fabricIndex - FabricIndex for which to add a new operational certificate chain + * @param noc - Buffer containing the NOC certificate to add + * @param icac - Buffer containing the ICAC certificate to add. If no ICAC is needed, `icac.empty()` must be true. + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `noc` and `icac` cert copies + * @retval CHIP_ERROR_INVALID_ARGUMENT if either the noc or icac are invalid sizes + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if `fabricIndex` mismatches the one from previous successful + * `AddNewTrustedRootCertForFabric`. + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method + * is called after `UpdateOpCertsForFabric`, or if there was + * already a pending or persisted operational cert chain for the given `fabricIndex`, + * or if AddNewTrustedRootCertForFabric had not yet been called for the given `fabricIndex`. + * @retval other CHIP_ERROR value on internal errors + */ + virtual CHIP_ERROR AddNewOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) = 0; + + /** + * @brief Update and temporarily activate an existing operational certificate chain for the given fabric. + * + * The certificate chain is temporary until committed or reverted. + * The certificate chain is committed to storage on `CommitOpCertsForFabric`. + * The certificate chain is reverted to prior storage if `RevertPendingOpCerts` is called + * before `CommitOpCertsForFabric`. + * + * Only one pending operational certificate chain is supported at a time and it is illegal + * to call this method if there was not already a persisted certificate chain for the given + * fabric. + * + * Cryptographic signature verification or path validation is not enforced by this method. + * + * If `AddNewOpCertsForFabric` had been called before this method, this method will return + * CHIP_ERROR_INCORRECT_STATE since it is illegal to update a certificate chain after + * adding an existing NOC updating prior to a commit. + * + * If there is no existing persisted trusted root certificate for the given fabricIndex, this + * method will method will eturn CHIP_ERROR_INCORRECT_STATE since it is illegal in this + * implementation to store an NOC chain without associated root. + * + * @param fabricIndex - FabricIndex for which to add a new operational certificate chain + * @param noc - Buffer containing the NOC certificate to update + * @param icac - Buffer containing the ICAC certificate to update. If no ICAC is needed, `icac.empty()` must be true. + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `noc` and `icac` cert copies + * @retval CHIP_ERROR_INVALID_ARGUMENT if either the noc or icac are invalid sizes + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method + * is called after `AddNewOpCertsForFabric`, or if there was + * already a pending cert chain for the given `fabricIndex`, + * or if there is no associated persisted root for for the given `fabricIndex`. + * @retval other CHIP_ERROR value on internal errors + */ + virtual CHIP_ERROR UpdateOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) = 0; + + /** + * @brief Permanently commit the certificate chain last setup via successful calls to + * legal combinations of `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or + * `UpdateOpCertsForFabric`, replacing previously committed data, if any. + * + * This is to be used when CommissioningComplete is successfully received + * + * @param fabricIndex - FabricIndex for which to commit the certificate chain, used for security cross-checking + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, + * or if not valid pending state is available. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there are no pending certificate chain for `fabricIndex` + * @retval other CHIP_ERROR value on internal storage errors + */ + virtual CHIP_ERROR CommitOpCertsForFabric(FabricIndex fabricIndex) = 0; + + /** + * @brief Permanently remove the certificate chain associated with a fabric. + * + * This is to be used for fail-safe handling and RemoveFabric. Removes both the + * pending operational cert chain elements for the fabricIndex (if any) and the committed + * ones (if any). + * + * @param fabricIndex - FabricIndex for which to remove the operational cert chain + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there was not operational certificate data at all for `fabricIndex` + * @retval other CHIP_ERROR value on internal storage errors + */ + virtual CHIP_ERROR RemoveOpCertsForFabric(FabricIndex fabricIndex) = 0; + + /** + * @brief Permanently release the operational certificate chain made via successful calls to + * legal combinations of `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or + * `UpdateOpCertsForFabric`, if any. + * + * This is to be used when a fail-safe expires prior to CommissioningComplete. + * + * This method cannot error-out and must always succeed, even on a no-op. This should + * be safe to do given that `CommitOpCertsForFabric` must succeed to make an operation + * certificate chain usable. + */ + virtual void RevertPendingOpCerts() = 0; + + /** + * @brief Get the operational certificate element requested, giving the pending data or committed + * data depending on prior `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or + * `UpdateOpCertsForFabric` calls. + * + * On success, the `outCertificate` span is resized to the size of the actual certificate read-back. + * + * @param fabricIndex - fabricIndex for which to get the certificate + * @param element - which element of the cerficate chain to get + * @param outCertificate - buffer to contain the certificate obtained from persistent or temporary storage + * + * @retval CHIP_NO_ERROR on success. + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCertificate` does not fit the certificate. + * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized. + * @retval CHIP_ERROR_NOT_FOUND if the element cannot be found. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the fabricIndex is invalid. + * @retval other CHIP_ERROR value on internal storage errors. + */ + virtual CHIP_ERROR GetCertificate(FabricIndex fabricIndex, CertChainElement element, + MutableByteSpan & outCertificate) const = 0; +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/PersistentStorageOpCertStore.cpp b/src/credentials/PersistentStorageOpCertStore.cpp new file mode 100644 index 00000000000000..5cebd3adc430a1 --- /dev/null +++ b/src/credentials/PersistentStorageOpCertStore.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2022 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PersistentStorageOpCertStore.h" + +namespace chip { +namespace Credentials { + +namespace { + +using CertChainElement = OperationalCertificateStore::CertChainElement; + +const char * GetStorageKeyForCert(DefaultStorageKeyAllocator & keyAllocator, FabricIndex fabricIndex, CertChainElement element) +{ + const char * storageKey = nullptr; + + switch (element) + { + case CertChainElement::kNoc: + storageKey = keyAllocator.FabricNOC(fabricIndex); + break; + case CertChainElement::kIcac: + storageKey = keyAllocator.FabricICAC(fabricIndex); + break; + case CertChainElement::kRcac: + storageKey = keyAllocator.FabricRCAC(fabricIndex); + break; + default: + break; + } + + return storageKey; +} + +bool StorageHasCertificate(PersistentStorageDelegate * storage, FabricIndex fabricIndex, CertChainElement element) +{ + DefaultStorageKeyAllocator keyAllocator; + const char * storageKey = GetStorageKeyForCert(keyAllocator, fabricIndex, element); + + if (storageKey == nullptr) + { + return false; + } + + uint16_t keySize = 0; + CHIP_ERROR err = storage->SyncGetKeyValue(storageKey, nullptr, keySize); + + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + // Obviously not found + return false; + } + + if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + // On found, we actually expect an "error", since we didn't want to read it out. + return true; + } + + return false; +} + +CHIP_ERROR LoadCertFromStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex, CertChainElement element, + MutableByteSpan & outCert) +{ + DefaultStorageKeyAllocator keyAllocator; + const char * storageKey = GetStorageKeyForCert(keyAllocator, fabricIndex, element); + + uint16_t keySize = static_cast(outCert.size()); + CHIP_ERROR err = storage->SyncGetKeyValue(storageKey, outCert.data(), keySize); + + // Not finding an ICAC means we don't have one, so adjust to meet the API contract, where + // outCert.empty() will be true; + if ((element == CertChainElement::kIcac) && (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) + { + outCert.reduce_size(0); + return CHIP_ERROR_NOT_FOUND; + } + + if (err == CHIP_NO_ERROR) + { + outCert.reduce_size(keySize); + } + else if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + // Convert persisted storage error to CHIP_ERROR_NOT_FOUND so that + // `PersistentStorageOpCertStore::GetCertificate` doesn't need to convert. + err = CHIP_ERROR_NOT_FOUND; + } + + return err; +} + +CHIP_ERROR SaveCertToStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex, CertChainElement element, + const ByteSpan & cert) +{ + DefaultStorageKeyAllocator keyAllocator; + const char * storageKey = GetStorageKeyForCert(keyAllocator, fabricIndex, element); + + // If provided an empty ICAC, we delete the ICAC key previously used. If not there, it's OK + if ((element == CertChainElement::kIcac) && (cert.empty())) + { + CHIP_ERROR err = storage->SyncDeleteKeyValue(storageKey); + if ((err == CHIP_NO_ERROR) || (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) + { + return CHIP_NO_ERROR; + } + return err; + } + + return storage->SyncSetKeyValue(storageKey, cert.data(), static_cast(cert.size())); +} + +CHIP_ERROR DeleteCertFromStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex, CertChainElement element) +{ + DefaultStorageKeyAllocator keyAllocator; + const char * storageKey = GetStorageKeyForCert(keyAllocator, fabricIndex, element); + return storage->SyncDeleteKeyValue(storageKey); +} + +} // namespace + +bool PersistentStorageOpCertStore::HasPendingRootCert() const +{ + if (mStorage == nullptr) + { + return false; + } + + return (mPendingRcac.Get() != nullptr) && mStateFlags.Has(StateFlags::kAddNewTrustedRootCalled); +} + +bool PersistentStorageOpCertStore::HasPendingNocChain() const +{ + if (mStorage == nullptr) + { + return false; + } + + return (mPendingNoc.Get() != nullptr) && mStateFlags.HasAny(StateFlags::kAddNewOpCertsCalled, StateFlags::kUpdateOpCertsCalled); +} + +bool PersistentStorageOpCertStore::HasCertificateForFabric(FabricIndex fabricIndex, CertChainElement element) const +{ + if ((mStorage == nullptr) || !IsValidFabricIndex(fabricIndex)) + { + return false; + } + + // FabricIndex matches pending, we MAY have some pending data + if (fabricIndex == mPendingFabricIndex) + { + switch (element) + { + case CertChainElement::kRcac: + if (mPendingRcac.Get() != nullptr) + { + return true; + } + break; + case CertChainElement::kIcac: + if (mPendingIcac.Get() != nullptr) + { + return true; + } + // If we have a pending NOC and no pending ICAC, don't delegate to storage, return not found here + // since in the pending state, there truly is nothing. + if (mPendingNoc.Get() != nullptr) + { + return false; + } + break; + case CertChainElement::kNoc: + if (mPendingNoc.Get() != nullptr) + { + return true; + } + break; + default: + return false; + } + } + + return StorageHasCertificate(mStorage, fabricIndex, element); +} + +CHIP_ERROR PersistentStorageOpCertStore::AddNewTrustedRootCertForFabric(FabricIndex fabricIndex, const ByteSpan & rcac) +{ + ReturnErrorCodeIf(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(!IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + ReturnErrorCodeIf(rcac.empty() || (rcac.size() > Credentials::kMaxCHIPCertLength), CHIP_ERROR_INVALID_ARGUMENT); + + ReturnErrorCodeIf(mStateFlags.HasAny(StateFlags::kUpdateOpCertsCalled, StateFlags::kAddNewTrustedRootCalled, + StateFlags::kAddNewOpCertsCalled), + CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kRcac), CHIP_ERROR_INCORRECT_STATE); + + Platform::ScopedMemoryBuffer rcacBuf; + ReturnErrorCodeIf(!rcacBuf.Alloc(rcac.size()), CHIP_ERROR_NO_MEMORY); + memcpy(rcacBuf.Get(), rcac.data(), rcac.size()); + + mPendingRcac = std::move(rcacBuf); + mPendingRcacSize = static_cast(rcac.size()); + + mPendingFabricIndex = fabricIndex; + mStateFlags.Set(StateFlags::kAddNewTrustedRootCalled); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOpCertStore::AddNewOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, + const ByteSpan & icac) +{ + ReturnErrorCodeIf(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(!IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + ReturnErrorCodeIf(noc.empty() || (noc.size() > Credentials::kMaxCHIPCertLength), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(icac.size() > Credentials::kMaxCHIPCertLength, CHIP_ERROR_INVALID_ARGUMENT); + + // Can't have called UpdateOpCertsForFabric first, or called with pending certs + ReturnErrorCodeIf(mStateFlags.HasAny(StateFlags::kUpdateOpCertsCalled, StateFlags::kAddNewOpCertsCalled), + CHIP_ERROR_INCORRECT_STATE); + + // Need to have trusted roots installed to make the chain valid + ReturnErrorCodeIf(!mStateFlags.Has(StateFlags::kAddNewTrustedRootCalled), CHIP_ERROR_INCORRECT_STATE); + + // fabricIndex must match the current pending fabric + ReturnErrorCodeIf(fabricIndex != mPendingFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Can't have persisted NOC/ICAC for same fabric if adding + ReturnErrorCodeIf(StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kNoc), CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kIcac), CHIP_ERROR_INCORRECT_STATE); + + Platform::ScopedMemoryBuffer nocBuf; + ReturnErrorCodeIf(!nocBuf.Alloc(noc.size()), CHIP_ERROR_NO_MEMORY); + memcpy(nocBuf.Get(), noc.data(), noc.size()); + + Platform::ScopedMemoryBuffer icacBuf; + if (icac.size() > 0) + { + ReturnErrorCodeIf(!icacBuf.Alloc(icac.size()), CHIP_ERROR_NO_MEMORY); + memcpy(icacBuf.Get(), icac.data(), icac.size()); + } + + mPendingNoc = std::move(nocBuf); + mPendingNocSize = static_cast(noc.size()); + + mPendingIcac = std::move(icacBuf); + mPendingIcacSize = static_cast(icac.size()); + + mPendingFabricIndex = fabricIndex; + + mStateFlags.Set(StateFlags::kAddNewOpCertsCalled); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOpCertStore::UpdateOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, + const ByteSpan & icac) +{ + ReturnErrorCodeIf(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(!IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + ReturnErrorCodeIf(noc.empty() || (noc.size() > Credentials::kMaxCHIPCertLength), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(icac.size() > Credentials::kMaxCHIPCertLength, CHIP_ERROR_INVALID_ARGUMENT); + + // Can't have called AddNewOpCertsForFabric first, and should never get here after AddNewTrustedRootCertForFabric. + ReturnErrorCodeIf(mStateFlags.HasAny(StateFlags::kAddNewOpCertsCalled, StateFlags::kAddNewTrustedRootCalled), + CHIP_ERROR_INCORRECT_STATE); + + // Can't have already pending NOC from UpdateOpCerts not yet committed + ReturnErrorCodeIf(mStateFlags.HasAny(StateFlags::kUpdateOpCertsCalled), CHIP_ERROR_INCORRECT_STATE); + + // Need to have trusted roots installed to make the chain valid + ReturnErrorCodeIf(!StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kRcac), CHIP_ERROR_INCORRECT_STATE); + + // Must have persisted NOC for same fabric if updating + ReturnErrorCodeIf(!StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kNoc), CHIP_ERROR_INCORRECT_STATE); + + // Don't check for ICAC, we may not have had one before, but assume that if NOC is there, a + // previous chain was at least partially there + + Platform::ScopedMemoryBuffer nocBuf; + ReturnErrorCodeIf(!nocBuf.Alloc(noc.size()), CHIP_ERROR_NO_MEMORY); + memcpy(nocBuf.Get(), noc.data(), noc.size()); + + Platform::ScopedMemoryBuffer icacBuf; + if (icac.size() > 0) + { + ReturnErrorCodeIf(!icacBuf.Alloc(icac.size()), CHIP_ERROR_NO_MEMORY); + memcpy(icacBuf.Get(), icac.data(), icac.size()); + } + + mPendingNoc = std::move(nocBuf); + mPendingNocSize = static_cast(noc.size()); + + mPendingIcac = std::move(icacBuf); + mPendingIcacSize = static_cast(icac.size()); + + // For NOC update, UpdateOpCertsForFabric is what determines the pending fabric index, + // not a previous AddNewTrustedRootCertForFabric call. + mPendingFabricIndex = fabricIndex; + + mStateFlags.Set(StateFlags::kUpdateOpCertsCalled); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOpCertStore::CommitOpCertsForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + VerifyOrReturnError(HasPendingNocChain(), CHIP_ERROR_INCORRECT_STATE); + if (HasPendingRootCert()) + { + // Neither of these conditions should have occurred based on other interlocks, but since + // committing certificates is a dangerous operation, we absolutely validate our assumptions. + ReturnErrorCodeIf(mStateFlags.Has(StateFlags::kUpdateOpCertsCalled), CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(!mStateFlags.Has(StateFlags::kAddNewTrustedRootCalled), CHIP_ERROR_INCORRECT_STATE); + } + + // TODO: Handle transaction marking to revert partial certs at next boot if we get interrupted by reboot. + + // Start committing NOC first so we don't have dangling roots if one was added. + ByteSpan pendingNocSpan{ mPendingNoc.Get(), mPendingNocSize }; + CHIP_ERROR nocErr = SaveCertToStorage(mStorage, mPendingFabricIndex, CertChainElement::kNoc, pendingNocSpan); + + // ICAC storage handles deleting on empty/missing + ByteSpan pendingIcacSpan{ mPendingIcac.Get(), mPendingIcacSize }; + CHIP_ERROR icacErr = SaveCertToStorage(mStorage, mPendingFabricIndex, CertChainElement::kIcac, pendingIcacSpan); + + CHIP_ERROR rcacErr = CHIP_NO_ERROR; + if (HasPendingRootCert()) + { + ByteSpan pendingRcacSpan{ mPendingRcac.Get(), mPendingRcacSize }; + rcacErr = SaveCertToStorage(mStorage, mPendingFabricIndex, CertChainElement::kRcac, pendingRcacSpan); + } + + // Remember which was the first error, and if any error occurred. + CHIP_ERROR stickyErr = nocErr; + stickyErr = (stickyErr != CHIP_NO_ERROR) ? stickyErr : icacErr; + stickyErr = (stickyErr != CHIP_NO_ERROR) ? stickyErr : rcacErr; + + if (stickyErr != CHIP_NO_ERROR) + { + // On Adds rather than updates, remove anything possibly stored for the new fabric on partial + // failure. + if (mStateFlags.Has(StateFlags::kAddNewOpCertsCalled)) + { + (void) DeleteCertFromStorage(mStorage, mPendingFabricIndex, CertChainElement::kNoc); + (void) DeleteCertFromStorage(mStorage, mPendingFabricIndex, CertChainElement::kIcac); + } + if (mStateFlags.Has(StateFlags::kAddNewTrustedRootCalled)) + { + (void) DeleteCertFromStorage(mStorage, mPendingFabricIndex, CertChainElement::kRcac); + } + if (mStateFlags.Has(StateFlags::kUpdateOpCertsCalled)) + { + // Can't do anything to clean-up here, but pretty sure the fabric is broken now... + // TODO: Handle transaction marking to revert certs if somehow failing store on update by pre-backing-up opcerts + } + + return stickyErr; + } + + // If we got here, we succeeded and can reset the pending certs: next `GetCertificate` will use the stored certs + RevertPendingOpCerts(); + return CHIP_NO_ERROR; +} + +bool PersistentStorageOpCertStore::HasAnyCertificateForFabric(FabricIndex fabricIndex) const +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), false); + + bool rcacMissing = !StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kRcac); + bool icacMissing = !StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kIcac); + bool nocMissing = !StorageHasCertificate(mStorage, fabricIndex, CertChainElement::kNoc); + bool anyPending = (mPendingRcac.Get() != nullptr) || (mPendingIcac.Get() != nullptr) || (mPendingNoc.Get() != nullptr); + + // If there was *no* state, pending or persisted, we have an error + if (rcacMissing && icacMissing && nocMissing && !anyPending) + { + return false; + } + + return true; +} + +CHIP_ERROR PersistentStorageOpCertStore::RemoveOpCertsForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // If there was *no* state, pending or persisted, we have an error + ReturnErrorCodeIf(!HasAnyCertificateForFabric(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Clear any pending state + RevertPendingOpCerts(); + + // Remove all persisted certs for the given fabric, blindly + CHIP_ERROR nocErr = DeleteCertFromStorage(mStorage, fabricIndex, CertChainElement::kNoc); + CHIP_ERROR icacErr = DeleteCertFromStorage(mStorage, fabricIndex, CertChainElement::kIcac); + CHIP_ERROR rcacErr = DeleteCertFromStorage(mStorage, fabricIndex, CertChainElement::kRcac); + + // Ignore missing cert errors + nocErr = (nocErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : nocErr; + icacErr = (icacErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : icacErr; + rcacErr = (rcacErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : rcacErr; + + // Find the first error and return that + CHIP_ERROR stickyErr = nocErr; + stickyErr = (stickyErr != CHIP_NO_ERROR) ? stickyErr : icacErr; + stickyErr = (stickyErr != CHIP_NO_ERROR) ? stickyErr : rcacErr; + + return stickyErr; +} + +CHIP_ERROR PersistentStorageOpCertStore::GetPendingCertificate(FabricIndex fabricIndex, CertChainElement element, + MutableByteSpan & outCertificate) const +{ + if (fabricIndex != mPendingFabricIndex) + { + return CHIP_ERROR_NOT_FOUND; + } + + // FabricIndex matches pending, we MAY have some pending data + switch (element) + { + case CertChainElement::kRcac: + if (mPendingRcac.Get() != nullptr) + { + ByteSpan rcacSpan{ mPendingRcac.Get(), static_cast(mPendingRcacSize) }; + return CopySpanToMutableSpan(rcacSpan, outCertificate); + } + break; + case CertChainElement::kIcac: + if (mPendingIcac.Get() != nullptr) + { + ByteSpan icacSpan{ mPendingIcac.Get(), static_cast(mPendingIcacSize) }; + return CopySpanToMutableSpan(icacSpan, outCertificate); + } + break; + case CertChainElement::kNoc: + if (mPendingNoc.Get() != nullptr) + { + ByteSpan nocSpan{ mPendingNoc.Get(), static_cast(mPendingNocSize) }; + return CopySpanToMutableSpan(nocSpan, outCertificate); + } + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return CHIP_ERROR_NOT_FOUND; +} + +CHIP_ERROR PersistentStorageOpCertStore::GetCertificate(FabricIndex fabricIndex, CertChainElement element, + MutableByteSpan & outCertificate) const +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Handle case of pending data + CHIP_ERROR err = GetPendingCertificate(fabricIndex, element, outCertificate); + if ((err == CHIP_NO_ERROR) || (err != CHIP_ERROR_NOT_FOUND)) + { + // Found in pending, or got a deeper error: return the pending cert status. + return err; + } + + // If we have a pending NOC and no pending ICAC, don't delegate to storage, return not found here + // since in the pending state, there truly is nothing. + + if ((err == CHIP_ERROR_NOT_FOUND) && (element == CertChainElement::kIcac) && (mPendingNoc.Get() != nullptr)) + { + // Don't delegate to storage if we just have a pending NOC and are missing the ICAC + return CHIP_ERROR_NOT_FOUND; + } + + // Not found in pending, let's look in persisted + return LoadCertFromStorage(mStorage, fabricIndex, element, outCertificate); +} + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/PersistentStorageOpCertStore.h b/src/credentials/PersistentStorageOpCertStore.h new file mode 100644 index 00000000000000..3c2f36e7c53802 --- /dev/null +++ b/src/credentials/PersistentStorageOpCertStore.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "OperationalCertificateStore.h" + +namespace chip { +namespace Credentials { + +/** + * @brief OperationalCertificateStore implementation making use of PersistentStorageDelegate + * to load/store certificates. This is the legacy behavior of `FabricTable` prior + * to refactors to use `OperationalCertificateStore` and exists as a baseline example + * of how to use the interface. + */ +class PersistentStorageOpCertStore : public OperationalCertificateStore +{ +public: + PersistentStorageOpCertStore() = default; + virtual ~PersistentStorageOpCertStore() { Finish(); } + + /** + * @brief Initialize the certificate store to map to a given storage delegate. + * + * @param storage Pointer to persistent storage delegate to use. Must outlive this instance. + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if already initialized + */ + CHIP_ERROR Init(PersistentStorageDelegate * storage) + { + VerifyOrReturnError(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + RevertPendingOpCerts(); + mStorage = storage; + return CHIP_NO_ERROR; + } + + /** + * @brief Finalize the certificate sotre, so that subsequent operations fail + */ + void Finish() + { + VerifyOrReturn(mStorage != nullptr); + + RevertPendingOpCerts(); + mStorage = nullptr; + } + + bool HasPendingRootCert() const override; + bool HasPendingNocChain() const override; + + bool HasCertificateForFabric(FabricIndex fabricIndex, CertChainElement element) const override; + + CHIP_ERROR AddNewTrustedRootCertForFabric(FabricIndex fabricIndex, const ByteSpan & rcac) override; + CHIP_ERROR AddNewOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) override; + CHIP_ERROR UpdateOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) override; + + CHIP_ERROR CommitOpCertsForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR RemoveOpCertsForFabric(FabricIndex fabricIndex) override; + + void RevertPendingOpCerts() override + { + mPendingRcac.Free(); + mPendingIcac.Free(); + mPendingNoc.Free(); + + mPendingRcacSize = 0; + mPendingIcacSize = 0; + mPendingNocSize = 0; + + mPendingFabricIndex = kUndefinedFabricIndex; + mStateFlags.ClearAll(); + } + + CHIP_ERROR GetCertificate(FabricIndex fabricIndex, CertChainElement element, MutableByteSpan & outCertificate) const override; + +protected: + enum class StateFlags : uint8_t + { + // Below are flags to assist interlock logic + kAddNewOpCertsCalled = (1u << 0), + kAddNewTrustedRootCalled = (1u << 1), + kUpdateOpCertsCalled = (1u << 2), + }; + + // Returns CHIP_ERROR_NOT_FOUND if a pending certificate couldn't be found, otherwise status of pending copy + CHIP_ERROR GetPendingCertificate(FabricIndex fabricIndex, CertChainElement element, MutableByteSpan & outCertificate) const; + + // Returns true if any pending or persisted state exists for the fabricIndex, false if nothing at all is found. + bool HasAnyCertificateForFabric(FabricIndex fabricIndex) const; + + PersistentStorageDelegate * mStorage = nullptr; + + // This pending fabric index is `kUndefinedFabricIndex` if there are no pending certs at all for the fabric + FabricIndex mPendingFabricIndex = kUndefinedFabricIndex; + + Platform::ScopedMemoryBuffer mPendingRcac; + Platform::ScopedMemoryBuffer mPendingIcac; + Platform::ScopedMemoryBuffer mPendingNoc; + + uint16_t mPendingRcacSize = 0; + uint16_t mPendingIcacSize = 0; + uint16_t mPendingNocSize = 0; + + BitFlags mStateFlags; +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn index 5a31b6003b1c3e..164712b1dceb8c 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -47,6 +47,7 @@ chip_test_suite("tests") { "TestDeviceAttestationCredentials.cpp", "TestFabricTable.cpp", "TestGroupDataProvider.cpp", + "TestPersistentStorageOpCertStore.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/credentials/tests/TestPersistentStorageOpCertStore.cpp b/src/credentials/tests/TestPersistentStorageOpCertStore.cpp new file mode 100644 index 00000000000000..7920a063eb29c7 --- /dev/null +++ b/src/credentials/tests/TestPersistentStorageOpCertStore.cpp @@ -0,0 +1,806 @@ +/* + * + * Copyright (c) 2022 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::Credentials; +using CertChainElement = OperationalCertificateStore::CertChainElement; + +namespace { + +constexpr FabricIndex kFabricIndex1 = 1; +constexpr FabricIndex kFabricIndex2 = 2; +constexpr FabricIndex kOtherFabricIndex = static_cast(kFabricIndex1 + 10u); + +// The PersistentStorageOpCertStore does not validate cert contents, so we can use simple constants +const uint8_t kTestRcacBuf[] = { 'r', 'c', 'a', 'c' }; +const ByteSpan kTestRcacSpan{ kTestRcacBuf }; + +const uint8_t kTestIcacBuf[] = { 'i', 'c', 'a', 'c' }; +const ByteSpan kTestIcacSpan{ kTestIcacBuf }; + +const uint8_t kTestNocBuf[] = { 'n', 'o', 'c' }; +const ByteSpan kTestNocSpan{ kTestNocBuf }; + +void TestAddNocFlow(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + PersistentStorageOpCertStore opCertStore; + DefaultStorageKeyAllocator keyAllocator; + + // Failure before Init + CHIP_ERROR err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + + // Init succeeds + err = opCertStore.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Manually add existing root for the FabricIndex, should fail AddNewTrustedRootCertForFabric for + // same fabric but succeed GetCertificate. + const uint8_t kTestRcacBufExists[] = { 'r', 'c', 'a', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + + err = storageDelegate.SyncSetKeyValue(keyAllocator.FabricRCAC(kFabricIndex1), kTestRcacBufExists, sizeof(kTestRcacBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, + opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kRcac) == true); //< From manual add + + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + uint8_t largeBuf[400]; + MutableByteSpan largeSpan{ largeBuf }; + + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + + largeSpan = MutableByteSpan{ largeBuf }; + + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacBufExists })); + + // Adding root for another FabricIndex should work + err = opCertStore.AddNewTrustedRootCertForFabric(kUndefinedFabricIndex, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex2, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + // Should be able to read pending RCAC right away + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + // Trying to commit with pending RCAC but no NOC should fail but leave everything as-is + err = opCertStore.CommitOpCertsForFabric(kFabricIndex2); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + // Trying to do AddNewOpCertsForFabric for fabric different that with pending RCAC should fail + err = opCertStore.AddNewOpCertsForFabric(kOtherFabricIndex, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + // Clear other bad cases from storage for now + storageDelegate.ClearStorage(); + + // Trying to do AddNewOpCertsForFabric for same fabric as that with pending RCAC should fail + // if there are already existing NOC chain elements for the given fabric. + const uint8_t kTestIcacBufExists[] = { 'i', 'c', 'a', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + const uint8_t kTestNocBufExists[] = { 'n', 'o', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + + err = storageDelegate.SyncSetKeyValue(keyAllocator.FabricICAC(kFabricIndex2), kTestIcacBufExists, sizeof(kTestIcacBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + + err = storageDelegate.SyncSetKeyValue(keyAllocator.FabricNOC(kFabricIndex2), kTestNocBufExists, sizeof(kTestNocBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 2); + + NL_TEST_ASSERT(inSuite, + opCertStore.HasCertificateForFabric(kFabricIndex2, CertChainElement::kIcac) == true); //< From manual add + NL_TEST_ASSERT(inSuite, opCertStore.HasCertificateForFabric(kFabricIndex2, CertChainElement::kNoc) == true); //< From manual add + + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex2, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + storageDelegate.SyncDeleteKeyValue(keyAllocator.FabricICAC(kFabricIndex2)); + storageDelegate.SyncDeleteKeyValue(keyAllocator.FabricNOC(kFabricIndex2)); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + + // Trying to do AddNewOpCertsForFabric for same fabric as that with pending RCAC should succeed + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex2, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + // Should be able to get the pending cert even if not in persisted storage + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + + // Trying to do AddNewOpCertsForFabric a second time after success before commit should fail, + // but leave state as-is + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex2, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + // Should be able to get the pending cert even if not in persisted storage, after an API error + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + + // Trying to commit with wrong FabricIndex should fail + err = opCertStore.CommitOpCertsForFabric(kOtherFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + + // Commiting new certs should succeed on correct fabric + err = opCertStore.CommitOpCertsForFabric(kFabricIndex2); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< All certs now committed + + // Should be able to get the committed certs + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex2, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + + opCertStore.Finish(); +} + +void TestUpdateNocFlow(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + PersistentStorageOpCertStore opCertStore; + DefaultStorageKeyAllocator keyAllocator; + + // Failure before Init + CHIP_ERROR err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + + // Init succeeds + err = opCertStore.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Add a new pending trusted root to test for UpdateOpCertsForFabric failure on new root present + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Trying to do an UpdateOpCertsForFabric with new root pending should fail + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Revert state for next tests + opCertStore.RevertPendingOpCerts(); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + // Manually add root, ICAC and NOC to validate update since existing chain required + const uint8_t kTestRcacBufExists[] = { 'r', 'c', 'a', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + const uint8_t kTestIcacBufExists[] = { 'i', 'c', 'a', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + const uint8_t kTestNocBufExists[] = { 'n', 'o', 'c', ' ', 'e', 'x', 'i', 's', 't', 's' }; + + uint8_t largeBuf[400]; + MutableByteSpan largeSpan{ largeBuf }; + + { + err = + storageDelegate.SyncSetKeyValue(keyAllocator.FabricRCAC(kFabricIndex1), kTestRcacBufExists, sizeof(kTestRcacBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, + opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kRcac) == true); //< From manual add + + err = + storageDelegate.SyncSetKeyValue(keyAllocator.FabricICAC(kFabricIndex1), kTestIcacBufExists, sizeof(kTestIcacBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 2); + NL_TEST_ASSERT(inSuite, + opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kIcac) == true); //< From manual add + + err = storageDelegate.SyncSetKeyValue(keyAllocator.FabricNOC(kFabricIndex1), kTestNocBufExists, sizeof(kTestNocBufExists)); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); + NL_TEST_ASSERT(inSuite, + opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kNoc) == true); //< From manual add + + // Test that we can manually stored certs + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacBufExists })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacBufExists })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocBufExists })); + } + + // Update fails on fabric with wrong FabricIndex + err = opCertStore.UpdateOpCertsForFabric(kOtherFabricIndex, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Update succeeds on fabric with existing data + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); + + // Can read back existing root unchanged + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacBufExists })); + + // NOC chain elements see the pending updated certs + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacBuf })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocBuf })); + + // Trying update again fails + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); + + // Trying to add a new root after update, before commit/revert fails + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); + + // Trying to add new opcerts for any fabric after update, before commit/revert fails + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex2, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + // Committing writes the new values (we even "background-remove" the old ICAC/NOC before commit) + storageDelegate.SyncDeleteKeyValue(keyAllocator.FabricICAC(kFabricIndex1)); + storageDelegate.SyncDeleteKeyValue(keyAllocator.FabricNOC(kFabricIndex1)); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); //< Root remains + + err = opCertStore.CommitOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< All certs now committed + + // Should be able to get the committed cert even if not in persisted storage, after an API error + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacBufExists })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + + // Calling revert doesn't undo the work we just did + opCertStore.RevertPendingOpCerts(); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< All certs now committed + + // Verify the revert after commit left all data alone + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacBufExists })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + + // Verify that RemoveOpCertsForFabric fails on fabric with no data + err = opCertStore.RemoveOpCertsForFabric(kFabricIndex2); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); + + // Verify that RemoveOpCertsForFabric works for fabric we just updated + err = opCertStore.RemoveOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); // All keys gone + + opCertStore.Finish(); +} + +void TestReverts(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + PersistentStorageOpCertStore opCertStore; + + // Failure before Init + CHIP_ERROR err = opCertStore.RemoveOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + + // Init succeeds + err = opCertStore.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Add a new pending trusted root + uint8_t largeBuf[400]; + MutableByteSpan largeSpan{ largeBuf }; + + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Verify we can see the new trusted root + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestRcacSpan)); + + // Verify that after revert, we can't see the root anymore + opCertStore.RevertPendingOpCerts(); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + + { + // Add new root again, to then test review of AddNewTrustedCertificates + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Add NOC chain + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + } + + // Make sure we can see all pending certs before revert + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestRcacSpan)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestIcacSpan)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestNocSpan)); + } + + // Revert + opCertStore.RevertPendingOpCerts(); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + // Verify that after revert, we can't see the root or chain anymore + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + } + + // Start again to add a new set, but then let's commit + { + // Add new root again, to then test review of AddNewTrustedCertificates + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Add NOC chain + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex1, kTestNocSpan, kTestIcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + } + + // Commiting new certs should succeed on correct fabric + err = opCertStore.CommitOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< All certs now committed + + // Should be able to get the committed certs + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + } + + const uint8_t kNewNoc[] = { 'n', 'o', 'c', ' ', 'n', 'e', 'w' }; + + // Updating certs should work (NO ICAC) + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, ByteSpan{ kNewNoc }, ByteSpan{}); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< No change to keys + + // Should see committed root, pending NOC, absent ICAC + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + NL_TEST_ASSERT(inSuite, !opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kIcac)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewNoc })); + } + + // Revert, should be back at previous state + opCertStore.RevertPendingOpCerts(); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< Storage count did not yet change + + // Should be able to get the previously committed certs + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestIcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestNocSpan })); + } + + // Try again to update with missing ICAC and commit + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, ByteSpan{ kNewNoc }, ByteSpan{}); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< No change to keys + + err = opCertStore.CommitOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 2); //< ICAC cert should be gone + + // Should see committed root, new NOC, absent ICAC + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + NL_TEST_ASSERT(inSuite, opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kRcac)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + NL_TEST_ASSERT(inSuite, !opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kIcac)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewNoc })); + NL_TEST_ASSERT(inSuite, opCertStore.HasCertificateForFabric(kFabricIndex1, CertChainElement::kNoc)); + } + + opCertStore.Finish(); +} + +void TestRevertAddNoc(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + PersistentStorageOpCertStore opCertStore; + + // Init succeeds + CHIP_ERROR err = opCertStore.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Add a new pending trusted root + uint8_t largeBuf[400]; + MutableByteSpan largeSpan{ largeBuf }; + + { + // Add new root + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Add NOC chain, with NO ICAC + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex1, kTestNocSpan, ByteSpan{}); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + } + + // Make sure we get expected pending state before revert + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestRcacSpan)); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_NOT_FOUND); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(kTestNocSpan)); + } + + // Revert using RemoveOpCertsForFabric + err = opCertStore.RemoveOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + // Add again, and commit + { + // Add new root + err = opCertStore.AddNewTrustedRootCertForFabric(kFabricIndex1, kTestRcacSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + + // Add NOC chain, with NO ICAC + err = opCertStore.AddNewOpCertsForFabric(kFabricIndex1, kTestNocSpan, ByteSpan{}); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingRootCert() == true); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); //< Storage count did not yet increase + + err = opCertStore.CommitOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 2); //< We have RCAC, NOC, no ICAC + } + + // Update to add an ICAC + const uint8_t kNewIcac[] = { 'i', 'c', 'a', 'c', ' ', 'n', 'e', 'w' }; + const uint8_t kNewNoc[] = { 'n', 'o', 'c', ' ', 'n', 'e', 'w' }; + + // Updating certs should work (NO ICAC) + err = opCertStore.UpdateOpCertsForFabric(kFabricIndex1, ByteSpan{ kNewNoc }, ByteSpan{ kNewIcac }); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 2); //< No change to keys + + // Should see committed root, pending NOC, pending ICAC + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewIcac })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewNoc })); + } + + // Commit, should see the new ICAC appear. + err = opCertStore.CommitOpCertsForFabric(kFabricIndex1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingRootCert()); + NL_TEST_ASSERT(inSuite, !opCertStore.HasPendingNocChain()); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 3); //< We have RCAC, NOC, ICAC + + // Should see committed root, new NOC, new ICAC + { + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kRcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kTestRcacSpan })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kIcac, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewIcac })); + + largeSpan = MutableByteSpan{ largeBuf }; + err = opCertStore.GetCertificate(kFabricIndex1, CertChainElement::kNoc, largeSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, largeSpan.data_equal(ByteSpan{ kNewNoc })); + } + + opCertStore.Finish(); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Test AddNOC-like flows PersistentStorageOpCertStore", TestAddNocFlow), + NL_TEST_DEF("Test UpdateNOC-like flows PersistentStorageOpCertStore", TestUpdateNocFlow), + NL_TEST_DEF("Test revert operations of PersistentStorageOpCertStore", TestReverts), + NL_TEST_DEF("Test revert operations with AddNOC of PersistentStorageOpCertStore", TestRevertAddNoc), NL_TEST_SENTINEL() +}; + +/** + * Set up the test suite. + */ +int Test_Setup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + VerifyOrReturnError(error == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +int Test_Teardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +} // namespace + +/** + * Main + */ +int TestPersistentStorageOpCertStore() +{ + nlTestSuite theSuite = { "PersistentStorageOpCertStore tests", &sTests[0], Test_Setup, Test_Teardown }; + + // Run test suite againt one context. + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestPersistentStorageOpCertStore) diff --git a/src/crypto/OperationalKeystore.h b/src/crypto/OperationalKeystore.h index 29f13290d42f92..443542bf8b7bfe 100644 --- a/src/crypto/OperationalKeystore.h +++ b/src/crypto/OperationalKeystore.h @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2022 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. + */ + #pragma once #include @@ -44,7 +61,7 @@ class OperationalKeystore * * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either * `ActivateOpKeypairForFabric` is called. It is destroyed if - * `RevertPendingKeypair` or `Finish` is called before `CommitOpKeypairForFabric`. + * `RevertPendingKeypair` is called before `CommitOpKeypairForFabric`. * If a pending keypair already existed for the given `fabricIndex`, it is replaced by this call. * * Only one pending operational keypair is supported at a time.