Skip to content

Commit

Permalink
Introduce fail-safe compliant Operational Cert storage
Browse files Browse the repository at this point in the history
- This PR is an intermediate fully unit-tested step towards replacing
  all NOC/ICAC/RCAC storage from Fabric-Table to allow spec-compliant
  fail-safe implementation
- Right now, we have hacky code in opcreds cluster server and in FabricTable
  to attempt to affect fail-safe properly. It doesn't actually work and
  prevents the TrustedRootCertificates, NOCs and Fabrics attributes from
  being implemented properly, and prevents UpdateNOC from being implemented
  to spec

Issue project-chip#18633
Issue project-chip#17208
Issue project-chip#15585

This PR implements an operational cert storage interface and provides
an implementation based on exact storage from existing FabricTable
for backwards compatibility. Its usage in FabricTable and via
opcreds cluster is a follow-up.

Testing done:
- Added large-scale new code unit test
- Unit tests pass
- Not hooked-up to device software yet, so just the unit tests need to pass
  • Loading branch information
tcarmelveilleux committed Jun 15, 2022
1 parent dcec35a commit 5c8d60b
Show file tree
Hide file tree
Showing 7 changed files with 1,701 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/credentials/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
247 changes: 247 additions & 0 deletions src/credentials/OperationalCertificateStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*
* 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 <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/Span.h>

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
Loading

0 comments on commit 5c8d60b

Please sign in to comment.