Coverage Report

Created: 2025-06-24 06:17

/src/connectedhomeip/src/credentials/OperationalCertificateStore.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 *    Copyright (c) 2022 Project CHIP Authors
3
 *    All rights reserved.
4
 *
5
 *    Licensed under the Apache License, Version 2.0 (the "License");
6
 *    you may not use this file except in compliance with the License.
7
 *    You may obtain a copy of the License at
8
 *
9
 *        http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *    Unless required by applicable law or agreed to in writing, software
12
 *    distributed under the License is distributed on an "AS IS" BASIS,
13
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *    See the License for the specific language governing permissions and
15
 *    limitations under the License.
16
 */
17
18
#pragma once
19
20
#include <lib/core/CHIPError.h>
21
#include <lib/core/DataModelTypes.h>
22
#include <lib/support/Span.h>
23
24
namespace chip {
25
namespace Credentials {
26
27
class OperationalCertificateStore
28
{
29
public:
30
    enum class CertChainElement : uint8_t
31
    {
32
        kRcac = 0,
33
        kIcac = 1,
34
        kNoc  = 2
35
    };
36
37
    enum class VidVerificationElement : uint8_t
38
    {
39
        kVidVerificationStatement = 0,
40
        kVvsc                     = 1,
41
    };
42
43
0
    virtual ~OperationalCertificateStore() {}
44
45
    // ==== API designed for commisionables to support fail-safe (although can be used by controllers) ====
46
47
    /**
48
     * @brief Returns true if a pending root certificate exists and is active from a previous
49
     *        `AddNewTrustedRootCertForFabric`.
50
     */
51
    virtual bool HasPendingRootCert() const = 0;
52
53
    /**
54
     * @brief Returns true if a pending operational certificate chain exists and is active from a previous
55
     *        `AddNewOpCertsForFabric` or `UpdateOpCertsForFabric`.
56
     */
57
    virtual bool HasPendingNocChain() const = 0;
58
59
    /**
60
     * @brief Returns true if either a pending VVSC or VIDVerificationStatement exists and is active.
61
     */
62
    virtual bool HasPendingVidVerificationElements() const
63
0
    {
64
        // Default false to match the CHIP_ERROR_NOT_IMPLEMENTED for default versions in this base class.
65
0
        return false;
66
0
    }
67
68
    /**
69
     * @brief Returns whether a usable operational certificates chain exists for the given fabric.
70
     *
71
     * Returns true even if the certificates are not persisted yet. Only returns true if a certificate
72
     * is presently usable such that `GetCertificate` would succeed for the fabric.
73
     *
74
     * @param fabricIndex - FabricIndex for which availability of certificate will be checked.
75
     * @param element - Element of the certificate chain whose presence needs to be checked
76
     * @return true if there an active obtainable operational certificate of the given type for the given FabricIndex,
77
     *         false otherwise.
78
     */
79
    virtual bool HasCertificateForFabric(FabricIndex fabricIndex, CertChainElement element) const = 0;
80
81
    /**
82
     * @brief Add and temporarily activate a new Trusted Root Certificate for the given fabric
83
     *
84
     * The certificate is temporary until committed or reverted.
85
     * The certificate is committed to storage only on `CommitOpCertsForFabric`.
86
     * The certificate is destroyed if `RevertPendingOpCerts` is called before `CommitOpCertsForFabric`.
87
     *
88
     * Only one pending trusted root certificate is supported at a time and it is illegal
89
     * to call this method if there is already a persisted root certificate for the given
90
     * fabric.
91
     *
92
     * Uniqueness constraints for roots (see AddTrustedRootCertificate command in spec) are not
93
     * enforced by this method and must be done as a more holistic check elsewhere. Cryptographic
94
     * signature verification or path validation are not enforced by this method.
95
     *
96
     * If `UpdateOpCertsForFabric` had been called before this method, this method will return
97
     * CHIP_ERROR_INCORRECT_STATE since it is illegal to update trusted roots when updating an
98
     * existing NOC chain.
99
     *
100
     * @param fabricIndex - FabricIndex for which a new trusted root certificate should be added
101
     * @param rcac - Buffer containing the root certificate to add.
102
     *
103
     * @retval CHIP_NO_ERROR on success
104
     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary root cert
105
     * @retval CHIP_ERROR_INVALID_ARGUMENT if the certificate is empty or too large
106
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method
107
     *                                    is called after `UpdateOpCertsForFabric`, or if there was
108
     *                                    already a pending or persisted root certificate for the given `fabricIndex`.
109
     * @retval other CHIP_ERROR value on internal errors
110
     */
111
    virtual CHIP_ERROR AddNewTrustedRootCertForFabric(FabricIndex fabricIndex, const ByteSpan & rcac) = 0;
112
113
    /**
114
     * @brief Add and temporarily activate an operational certificate chain for the given fabric.
115
     *
116
     * The certificate chain is temporary until committed or reverted.
117
     * The certificate chain is committed to storage on `CommitOpCertsForFabric`.
118
     * The certificate chain is destroyed if `RevertPendingOpCerts` is called before `CommitOpCertsForFabric`.
119
     *
120
     * Only one pending operational certificate chain is supported at a time and it is illegal
121
     * to call this method if there is already a persisted certificate chain for the given
122
     * fabric.
123
     *
124
     * Cryptographic signature verification or path validation are not enforced by this method.
125
     *
126
     * If `UpdateOpCertsForFabric` had been called before this method, this method will return
127
     * CHIP_ERROR_INCORRECT_STATE since it is illegal to add a certificate chain after
128
     * updating an existing NOC and before committing or reverting the update.
129
     *
130
     * If `AddNewTrustedRootCertForFabric` had not been called before this method, this method will
131
     * return CHIP_ERROR_INCORRECT_STATE since it is illegal in this implementation to store an
132
     * NOC chain without associated root.
133
     *
134
     * NOTE: The Matter spec allows AddNOC without AddTrustedRootCertificate if the NOC
135
     * chains to an existing root, to support root reuse. In this implementation, we expect each
136
     * fabric to store the root with the rest of the chain. Because of this, callers must ensure
137
     * that if an AddNOC command is done and no trusted root was added, that the requisite existing
138
     * root be "copied over" to match.
139
     *
140
     * @param fabricIndex - FabricIndex for which to add a new operational certificate chain
141
     * @param noc - Buffer containing the NOC certificate to add
142
     * @param icac - Buffer containing the ICAC certificate to add. If no ICAC is needed, `icac.empty()` must be true.
143
     *
144
     * @retval CHIP_NO_ERROR on success
145
     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `noc` and `icac` cert copies
146
     * @retval CHIP_ERROR_INVALID_ARGUMENT if either the noc or icac are invalid sizes
147
     * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if `fabricIndex` mismatches the one from a previous successful
148
     *                                         `AddNewTrustedRootCertForFabric`.
149
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method
150
     *                                    is called after `UpdateOpCertsForFabric`, if there was
151
     *                                    already a pending or persisted operational cert chain for the given `fabricIndex`, or
152
     *                                    if AddNewTrustedRootCertForFabric had not yet been called for the given `fabricIndex`.
153
     *
154
     * @retval other CHIP_ERROR value on internal errors
155
     */
156
    virtual CHIP_ERROR AddNewOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) = 0;
157
158
    /**
159
     * @brief Update and temporarily activate an existing operational certificate chain for the given fabric.
160
     *
161
     * The certificate chain is temporary until committed or reverted.
162
     * The certificate chain is committed to storage on `CommitOpCertsForFabric`.
163
     * The certificate chain is reverted to prior storage if `RevertPendingOpCerts` is called
164
     * before `CommitOpCertsForFabric`.
165
     *
166
     * Only one pending operational certificate chain is supported at a time and it is illegal
167
     * to call this method if there was not already a persisted certificate chain for the given
168
     * fabric.
169
     *
170
     * Cryptographic signature verification or path validation are not enforced by this method.
171
     *
172
     * If `AddNewOpCertsForFabric` had been called before this method, this method will return
173
     * CHIP_ERROR_INCORRECT_STATE since it is illegal to update a certificate chain after
174
     * adding an existing NOC and before committing or reverting the addition.
175
     *
176
     * If there is no existing persisted trusted root certificate and NOC chain for the given
177
     * fabricIndex, this method will return CHIP_ERROR_INCORRECT_STATE since it is
178
     * illegal in this implementation to store an NOC chain without associated root, and it is illegal
179
     * to update an opcert for a fabric not already configured.
180
     *
181
     * @param fabricIndex - FabricIndex for which to update the operational certificate chain
182
     * @param noc - Buffer containing the new NOC certificate to use
183
     * @param icac - Buffer containing the ICAC certificate to use. If no ICAC is needed, `icac.empty()` must be true.
184
     *
185
     * @retval CHIP_NO_ERROR on success
186
     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `noc` and `icac` cert copies
187
     * @retval CHIP_ERROR_INVALID_ARGUMENT if either the noc or icac are invalid sizes
188
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, if this method
189
     *                                    is called after `AddNewOpCertsForFabric`, if there was
190
     *                                    already a pending cert chain for the given `fabricIndex`, if there are
191
     *                                    no associated persisted root and NOC chain for the given `fabricIndex`.
192
     * @retval other CHIP_ERROR value on internal errors
193
     */
194
    virtual CHIP_ERROR UpdateOpCertsForFabric(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac) = 0;
195
196
    /**
197
     * @brief Permanently commit the certificate chain last configured via successful calls to
198
     *        legal combinations of `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or
199
     *        `UpdateOpCertsForFabric`, replacing previously committed data, if any.
200
     *
201
     * This is to be used when CommissioningComplete is successfully received
202
     *
203
     * @param fabricIndex - FabricIndex for which to commit the certificate chain, used for security cross-checking
204
     *
205
     * @retval CHIP_NO_ERROR on success
206
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized,
207
     *                                    or if no valid pending state is available.
208
     * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no pending certificate chain for `fabricIndex`
209
     * @retval other CHIP_ERROR value on internal storage errors
210
     */
211
    virtual CHIP_ERROR CommitOpCertsForFabric(FabricIndex fabricIndex) = 0;
212
213
    /**
214
     * @brief Permanently remove the certificate chain associated with a fabric.
215
     *
216
     * This is to be used for RemoveFabric. Removes both the pending operational cert chain
217
     * elements for the fabricIndex (if any) and the committed ones (if any).
218
     *
219
     * This must also remove any VID Verification statement elements, if they exist, since
220
     * those are associated with the opcerts.
221
     *
222
     * @param fabricIndex - FabricIndex for which to remove the operational cert chain
223
     *
224
     * @retval CHIP_NO_ERROR on success
225
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized.
226
     * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there was no operational certificate data at all for `fabricIndex`
227
     * @retval other CHIP_ERROR value on internal storage errors
228
     */
229
    virtual CHIP_ERROR RemoveOpCertsForFabric(FabricIndex fabricIndex) = 0;
230
231
    /**
232
     * @brief Permanently release the operational certificate chain made via successful calls to
233
     *        legal combinations of `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or
234
     *        `UpdateOpCertsForFabric`, if any.
235
     *
236
     * This is to be used when a fail-safe expires prior to CommissioningComplete.
237
     *
238
     * This method cannot error-out and must always succeed, even on a no-op. This should
239
     * be safe to do given that `CommitOpCertsForFabric` must succeed to make an operation
240
     * certificate chain usable.
241
     */
242
    virtual void RevertPendingOpCerts() = 0;
243
244
    /**
245
     * @brief Update the VidVerificationSigningCertificate (VVSC) for the given fabric, including
246
     *        possibly removing it (if an empty vvsc buffer is provided).
247
     *
248
     * If a fabric was pending, the certificate is temporary until committed by `CommitOpCertsForFabric`
249
     * or reverted by `RevertPendingOpCerts`. Otherwise it is immediately commited/erased.
250
     *
251
     * Only one pending VVSC certificate is supported at a time and it is illegal
252
     * to call this method if there was is not already an operational certificate chain
253
     * pending or committed for the given fabric.
254
     *
255
     * Cryptographic signature verification or path validation are not enforced by this method.
256
     *
257
     * If there is no existing persisted or pending trusted root certificate and NOC chain for the given
258
     * fabricIndex, this method will return CHIP_ERROR_INCORRECT_STATE since it is
259
     * illegal in this implementation to store a VVSC without a fabric already there.
260
     *
261
     * @param fabricIndex - FabricIndex for which to update the VidVerificationSigningCert
262
     * @param vvsc - Buffer containing the VVSC payload
263
     *
264
     * @retval CHIP_NO_ERROR on success
265
     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `vvsc` cert copy
266
     * @retval CHIP_ERROR_INVALID_ARGUMENT if the VVSC is an invalid size
267
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, or if there are
268
     *                                    no associated root and NOC chain for the given `fabricIndex`.
269
     * @retval CHIP_ERROR_NOT_IMPLEMENTED if this method is not implemented (e.g. for simple controller-only cases).
270
     * @retval other CHIP_ERROR value on internal errors
271
     */
272
    virtual CHIP_ERROR UpdateVidVerificationSignerCertForFabric(FabricIndex fabricIndex, ByteSpan vvsc)
273
0
    {
274
0
        return CHIP_ERROR_NOT_IMPLEMENTED;
275
0
    }
276
277
    /**
278
     * @brief Update the VidVerificationStatement for the given fabric, including
279
     *        possibly removing it (if an empty `vidVerificationStatement` buffer is provided).
280
     *
281
     * If a fabric was pending, the statement is temporary until committed by `CommitOpCertsForFabric`
282
     * or reverted by `RevertPendingOpCerts`. Otherwise it is immediately commited/erased.
283
     *
284
     * Only one pending statement is supported at a time and it is illegal
285
     * to call this method if there was is not already an operational certificate chain
286
     * pending or committed for the given fabric.
287
     *
288
     * If there is no existing persisted or pending trusted root certificate and NOC chain for the given
289
     * fabricIndex, this method will return CHIP_ERROR_INCORRECT_STATE since it is
290
     * illegal in this implementation to store a VVSC without a fabric already there.
291
     *
292
     * @param fabricIndex - FabricIndex for which to update the VidVerificationSigningCert
293
     * @param vidVerificationStatement - Buffer containing the VidVerificationStatement payload
294
     *
295
     * @retval CHIP_NO_ERROR on success
296
     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to maintain the temporary `vidVerificatioNStatement` copy
297
     * @retval CHIP_ERROR_INVALID_ARGUMENT if the vidVerificationStatement is an invalid format
298
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized, or if there are
299
     *                                    no associated root and NOC chain for the given `fabricIndex`.
300
     * @retval CHIP_ERROR_NOT_IMPLEMENTED if this method is not implemented (e.g. for simple controller-only cases).
301
     * @retval other CHIP_ERROR value on internal errors
302
     */
303
    virtual CHIP_ERROR UpdateVidVerificationStatementForFabric(FabricIndex fabricIndex, ByteSpan vidVerificationStatement)
304
0
    {
305
0
        return CHIP_ERROR_NOT_IMPLEMENTED;
306
0
    }
307
308
    /**
309
     * @brief Same as RevertPendingOpCerts(), but leaves pending Trusted Root certs if they had
310
     *        been added. This is is an operation to support the complex error handling of
311
     *        AddNOC, where we don't want to have "sticking" ICAC/NOC after validation
312
     *        problems, but don't want to lose the RCAC given in an AddTrustedRootCertificate
313
     *        command.
314
     */
315
    virtual void RevertPendingOpCertsExceptRoot() = 0;
316
317
    /**
318
     * @brief Get the operational certificate element requested, giving the pending data or committed
319
     *        data depending on prior `AddNewTrustedRootCertForFabric`, `AddNewOpCertsForFabric` or
320
     *        `UpdateOpCertsForFabric` calls.
321
     *
322
     * On success, the `outCertificate` span is resized to the size of the actual certificate read-back.
323
     *
324
     * @param fabricIndex - fabricIndex for which to get the certificate
325
     * @param element - which element of the cerficate chain to get
326
     * @param outCertificate - buffer to contain the certificate obtained from persistent or temporary storage
327
     *
328
     * @retval CHIP_NO_ERROR on success.
329
     * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCertificate` is too small to fit the certificate found.
330
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized.
331
     * @retval CHIP_ERROR_NOT_FOUND if the element cannot be found.
332
     * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the fabricIndex is invalid.
333
     * @retval other CHIP_ERROR value on internal storage errors.
334
     */
335
    virtual CHIP_ERROR GetCertificate(FabricIndex fabricIndex, CertChainElement element,
336
                                      MutableByteSpan & outCertificate) const = 0;
337
338
    /**
339
     * @brief Get the VidVerification element requested, giving the pending data or committed
340
     *        data depending on prior `UpdateVidVerificationSignerCertForFabric`, or
341
     *        `UpdateVidVerificationStatemmentForFabric` calls.
342
     *
343
     * If element is not found, outElement is resized to 0 bytes (empty).
344
     *
345
     * On success, the `outElement` span is resized to the size of the actual element read-back.
346
     *
347
     * @param fabricIndex - fabricIndex for which to get the certificate
348
     * @param element - which element to get
349
     * @param outElement- buffer to contain the element obtained from persistent or temporary storage
350
     *
351
     * @retval CHIP_NO_ERROR on success.
352
     * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outElement` is too small to fit the certificate found.
353
     * @retval CHIP_ERROR_INCORRECT_STATE if the certificate store is not properly initialized.
354
     * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the fabricIndex is invalid.
355
     * @retval CHIP_ERROR_NOT_IMPLEMENTED if this method is not implemented (e.g. for simple controller-only cases).
356
     * @retval other CHIP_ERROR value on internal storage errors.
357
     */
358
    virtual CHIP_ERROR GetVidVerificationElement(FabricIndex fabricIndex, VidVerificationElement element,
359
                                                 MutableByteSpan & outElement) const
360
0
    {
361
0
        return CHIP_ERROR_NOT_IMPLEMENTED;
362
0
    }
363
};
364
365
/**
366
 * @brief RAII class to operate on an OperationalCertificateStore with auto-revert if not committed.
367
 *
368
 * Use as:
369
 *
370
 * CHIP_ERROR FunctionWillReturnWithPendingReverted(....)
371
 * {
372
 *     OpCertStoreTransaction transaction(opCertStore);
373
 *
374
 *     ReturnErrorOnFailure(transaction->AddNewTrustedRootCertForFabric(...));
375
 *     ReturnErrorOnFailure(transaction->AddNewOpCertsForFabric(...));
376
 *     ReturnErrorOnFailure(transaction->CommitOpCertsForFabric(...));
377
 *
378
 *     return CHIP_NO_ERROR;
379
 * }
380
 */
381
class OpCertStoreTransaction
382
{
383
public:
384
0
    explicit OpCertStoreTransaction(OperationalCertificateStore & store) : mStore(store) {}
385
    ~OpCertStoreTransaction()
386
0
    {
387
0
        // This is a no-op if CommitOpCertsForFabric had been called on the store
388
0
        mStore.RevertPendingOpCerts();
389
0
    }
390
391
    // Non-copyable
392
    OpCertStoreTransaction(OpCertStoreTransaction const &) = delete;
393
    void operator=(OpCertStoreTransaction const &)         = delete;
394
395
0
    OperationalCertificateStore * operator->() { return &mStore; }
396
397
private:
398
    OperationalCertificateStore & mStore;
399
};
400
401
} // namespace Credentials
402
} // namespace chip