Skip to content

Commit fa64d3e

Browse files
authored
Merge branch 'release/6.x' into f/backport-snapshot-cose-receipt
2 parents d433b50 + de7b364 commit fa64d3e

File tree

8 files changed

+363
-26
lines changed

8 files changed

+363
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2121

2222
- Fix race condition when initialising a ledger secret's commit secret (#7689)
2323
- Add missing cases for `FailedInvalidCPUID` and `FailedInvalidTcbVersion` in quote verification error handling (#7696).
24+
- On recovery, the UVM descriptor SVN is now set to the minimum of the previously stored value in the KV and the value found in the new node's startup endorsements. On start, the behaviour is unchanged (#7716).
2425

2526
## [6.0.23]
2627

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,15 @@ if(BUILD_TESTS)
10051005
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/tx_status_test.cpp
10061006
)
10071007

1008+
add_unit_test(
1009+
internal_tables_access_test
1010+
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/internal_tables_access_test.cpp
1011+
${CCF_DIR}/src/node/uvm_endorsements.cpp
1012+
)
1013+
target_link_libraries(
1014+
internal_tables_access_test PRIVATE ccfcrypto.host ccf_kv.host
1015+
)
1016+
10081017
add_unit_test(
10091018
node_frontend_test
10101019
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/node_frontend_test.cpp

src/node/rpc/node_frontend.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1636,7 +1636,7 @@ namespace ccf
16361636
ctx.tx, host_data, in.snp_security_policy);
16371637

16381638
InternalTablesAccess::trust_node_uvm_endorsements(
1639-
ctx.tx, in.snp_uvm_endorsements);
1639+
ctx.tx, in.snp_uvm_endorsements, recovering);
16401640

16411641
auto attestation =
16421642
AttestationProvider::get_snp_attestation(in.quote_info).value();
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
4+
#include "ccf/app_interface.h"
5+
#include "ccf/service/tables/host_data.h"
6+
#include "ccf/service/tables/service.h"
7+
#include "crypto/openssl/hash.h"
8+
#include "service/tables/config.h"
9+
#include "service/tables/signatures.h"
10+
11+
#define DOCTEST_CONFIG_IMPLEMENT
12+
13+
#include "kv/store.h"
14+
#include "kv/test/null_encryptor.h"
15+
#include "service/internal_tables_access.h"
16+
17+
#include <doctest/doctest.h>
18+
19+
using namespace ccf;
20+
21+
TEST_CASE("trust_node_uvm_endorsements - not recovering, empty map")
22+
{
23+
ccf::kv::Store kv_store;
24+
auto encryptor = std::make_shared<ccf::kv::NullTxEncryptor>();
25+
kv_store.set_encryptor(encryptor);
26+
27+
SNPUVMEndorsements table(Tables::NODE_SNP_UVM_ENDORSEMENTS);
28+
29+
pal::UVMEndorsements endorsement{"did:x509:test", "test-feed", "42"};
30+
31+
{
32+
auto tx = kv_store.create_tx();
33+
InternalTablesAccess::trust_node_uvm_endorsements(
34+
tx, endorsement, false /* recovering */);
35+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
36+
}
37+
38+
{
39+
auto tx = kv_store.create_read_only_tx();
40+
auto handle = tx.ro(table);
41+
auto result = handle->get("did:x509:test");
42+
REQUIRE(result.has_value());
43+
REQUIRE(result->size() == 1);
44+
auto it = result->find("test-feed");
45+
REQUIRE(it != result->end());
46+
REQUIRE(it->second.svn == "42");
47+
}
48+
}
49+
50+
TEST_CASE("trust_node_uvm_endorsements - recovering, new DID not in map")
51+
{
52+
ccf::kv::Store kv_store;
53+
auto encryptor = std::make_shared<ccf::kv::NullTxEncryptor>();
54+
kv_store.set_encryptor(encryptor);
55+
56+
SNPUVMEndorsements table(Tables::NODE_SNP_UVM_ENDORSEMENTS);
57+
58+
// Pre-populate with an existing DID
59+
{
60+
auto tx = kv_store.create_tx();
61+
auto handle = tx.rw(table);
62+
FeedToEndorsementsDataMap existing;
63+
existing["existing-feed"] = {"100"};
64+
handle->put("did:x509:existing", existing);
65+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
66+
}
67+
68+
// Call with a different DID while recovering
69+
pal::UVMEndorsements endorsement{"did:x509:new", "new-feed", "50"};
70+
71+
{
72+
auto tx = kv_store.create_tx();
73+
InternalTablesAccess::trust_node_uvm_endorsements(
74+
tx, endorsement, true /* recovering */);
75+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
76+
}
77+
78+
// Verify new DID was written
79+
{
80+
auto tx = kv_store.create_read_only_tx();
81+
auto handle = tx.ro(table);
82+
83+
auto new_result = handle->get("did:x509:new");
84+
REQUIRE(new_result.has_value());
85+
REQUIRE(new_result->size() == 1);
86+
auto it = new_result->find("new-feed");
87+
REQUIRE(it != new_result->end());
88+
REQUIRE(it->second.svn == "50");
89+
90+
// Prior contents unchanged
91+
auto existing_result = handle->get("did:x509:existing");
92+
REQUIRE(existing_result.has_value());
93+
REQUIRE(existing_result->size() == 1);
94+
auto eit = existing_result->find("existing-feed");
95+
REQUIRE(eit != existing_result->end());
96+
REQUIRE(eit->second.svn == "100");
97+
}
98+
}
99+
100+
TEST_CASE("trust_node_uvm_endorsements - recovering, existing DID, new feed")
101+
{
102+
ccf::kv::Store kv_store;
103+
auto encryptor = std::make_shared<ccf::kv::NullTxEncryptor>();
104+
kv_store.set_encryptor(encryptor);
105+
106+
SNPUVMEndorsements table(Tables::NODE_SNP_UVM_ENDORSEMENTS);
107+
108+
// Pre-populate with an existing DID and feed
109+
{
110+
auto tx = kv_store.create_tx();
111+
auto handle = tx.rw(table);
112+
FeedToEndorsementsDataMap existing;
113+
existing["feed-A"] = {"100"};
114+
handle->put("did:x509:shared", existing);
115+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
116+
}
117+
118+
// Call with the same DID but a different feed while recovering
119+
pal::UVMEndorsements endorsement{"did:x509:shared", "feed-B", "75"};
120+
121+
{
122+
auto tx = kv_store.create_tx();
123+
InternalTablesAccess::trust_node_uvm_endorsements(
124+
tx, endorsement, true /* recovering */);
125+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
126+
}
127+
128+
// Verify both feeds are present
129+
{
130+
auto tx = kv_store.create_read_only_tx();
131+
auto handle = tx.ro(table);
132+
133+
auto result = handle->get("did:x509:shared");
134+
REQUIRE(result.has_value());
135+
REQUIRE(result->size() == 2);
136+
137+
auto it_a = result->find("feed-A");
138+
REQUIRE(it_a != result->end());
139+
REQUIRE(it_a->second.svn == "100");
140+
141+
auto it_b = result->find("feed-B");
142+
REQUIRE(it_b != result->end());
143+
REQUIRE(it_b->second.svn == "75");
144+
}
145+
}
146+
147+
TEST_CASE(
148+
"trust_node_uvm_endorsements - recovering, existing DID and feed, lower SVN")
149+
{
150+
ccf::kv::Store kv_store;
151+
auto encryptor = std::make_shared<ccf::kv::NullTxEncryptor>();
152+
kv_store.set_encryptor(encryptor);
153+
154+
SNPUVMEndorsements table(Tables::NODE_SNP_UVM_ENDORSEMENTS);
155+
156+
// Pre-populate with SVN 100, plus a separate unrelated DID
157+
{
158+
auto tx = kv_store.create_tx();
159+
auto handle = tx.rw(table);
160+
FeedToEndorsementsDataMap existing;
161+
existing["the-feed"] = {"100"};
162+
handle->put("did:x509:the-did", existing);
163+
164+
FeedToEndorsementsDataMap other;
165+
other["other-feed"] = {"999"};
166+
handle->put("did:x509:other-did", other);
167+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
168+
}
169+
170+
// Call with strictly lower SVN while recovering
171+
pal::UVMEndorsements endorsement{"did:x509:the-did", "the-feed", "42"};
172+
173+
{
174+
auto tx = kv_store.create_tx();
175+
InternalTablesAccess::trust_node_uvm_endorsements(
176+
tx, endorsement, true /* recovering */);
177+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
178+
}
179+
180+
// SVN should be updated to the lower value
181+
{
182+
auto tx = kv_store.create_read_only_tx();
183+
auto handle = tx.ro(table);
184+
185+
auto result = handle->get("did:x509:the-did");
186+
REQUIRE(result.has_value());
187+
REQUIRE(result->size() == 1);
188+
auto it = result->find("the-feed");
189+
REQUIRE(it != result->end());
190+
REQUIRE(it->second.svn == "42");
191+
192+
// Pre-existing unrelated DID is unchanged
193+
auto other_result = handle->get("did:x509:other-did");
194+
REQUIRE(other_result.has_value());
195+
REQUIRE(other_result->size() == 1);
196+
auto oit = other_result->find("other-feed");
197+
REQUIRE(oit != other_result->end());
198+
REQUIRE(oit->second.svn == "999");
199+
}
200+
}
201+
202+
TEST_CASE(
203+
"trust_node_uvm_endorsements - recovering, existing DID and feed, higher "
204+
"SVN")
205+
{
206+
ccf::kv::Store kv_store;
207+
auto encryptor = std::make_shared<ccf::kv::NullTxEncryptor>();
208+
kv_store.set_encryptor(encryptor);
209+
210+
SNPUVMEndorsements table(Tables::NODE_SNP_UVM_ENDORSEMENTS);
211+
212+
// Pre-populate with SVN 42
213+
{
214+
auto tx = kv_store.create_tx();
215+
auto handle = tx.rw(table);
216+
FeedToEndorsementsDataMap existing;
217+
existing["the-feed"] = {"42"};
218+
handle->put("did:x509:the-did", existing);
219+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
220+
}
221+
222+
// Call with strictly higher SVN while recovering
223+
pal::UVMEndorsements endorsement{"did:x509:the-did", "the-feed", "100"};
224+
225+
{
226+
auto tx = kv_store.create_tx();
227+
InternalTablesAccess::trust_node_uvm_endorsements(
228+
tx, endorsement, true /* recovering */);
229+
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
230+
}
231+
232+
// Map should be unchanged - SVN stays at 42
233+
{
234+
auto tx = kv_store.create_read_only_tx();
235+
auto handle = tx.ro(table);
236+
237+
auto result = handle->get("did:x509:the-did");
238+
REQUIRE(result.has_value());
239+
REQUIRE(result->size() == 1);
240+
auto it = result->find("the-feed");
241+
REQUIRE(it != result->end());
242+
REQUIRE(it->second.svn == "42");
243+
}
244+
}
245+
246+
int main(int argc, char** argv)
247+
{
248+
ccf::crypto::openssl_sha256_init();
249+
doctest::Context context;
250+
context.applyCommandLine(argc, argv);
251+
int res = context.run();
252+
ccf::crypto::openssl_sha256_shutdown();
253+
if (context.shouldExit())
254+
return res;
255+
return res;
256+
}

src/node/uvm_endorsements.cpp

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,27 @@
55

66
namespace ccf
77
{
8+
size_t parse_svn(const std::string& svn_str)
9+
{
10+
size_t svn = 0;
11+
auto result =
12+
std::from_chars(svn_str.data(), svn_str.data() + svn_str.size(), svn);
13+
if (result.ec != std::errc())
14+
{
15+
throw std::runtime_error(
16+
fmt::format("Unable to parse svn value {} to unsigned", svn_str));
17+
}
18+
return svn;
19+
}
20+
821
bool inline matches_uvm_roots_of_trust(
922
const pal::UVMEndorsements& endorsements,
1023
const std::vector<pal::UVMEndorsements>& uvm_roots_of_trust)
1124
{
1225
return std::ranges::any_of(
1326
uvm_roots_of_trust, [&](const auto& uvm_root_of_trust) {
14-
size_t root_of_trust_svn = 0;
15-
auto result = std::from_chars(
16-
uvm_root_of_trust.svn.data(),
17-
uvm_root_of_trust.svn.data() + uvm_root_of_trust.svn.size(),
18-
root_of_trust_svn);
19-
if (result.ec != std::errc())
20-
{
21-
throw std::runtime_error(fmt::format(
22-
"Unable to parse svn value {} to unsigned in UVM root of trust",
23-
uvm_root_of_trust.svn));
24-
}
25-
size_t endorsement_svn = 0;
26-
result = std::from_chars(
27-
endorsements.svn.data(),
28-
endorsements.svn.data() + endorsements.svn.size(),
29-
endorsement_svn);
30-
if (result.ec != std::errc())
31-
{
32-
throw std::runtime_error(fmt::format(
33-
"Unable to parse svn value {} to unsigned in UVM endorsements",
34-
endorsements.svn));
35-
}
27+
auto root_of_trust_svn = parse_svn(uvm_root_of_trust.svn);
28+
auto endorsement_svn = parse_svn(endorsements.svn);
3629

3730
return uvm_root_of_trust.did == endorsements.did &&
3831
uvm_root_of_trust.feed == endorsements.feed &&

src/node/uvm_endorsements.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ namespace ccf
4545
const std::vector<uint8_t>& uvm_endorsements_raw,
4646
const pal::PlatformAttestationMeasurement& uvm_measurement,
4747
const std::vector<pal::UVMEndorsements>& uvm_roots_of_trust);
48+
49+
size_t parse_svn(const std::string& svn_str);
4850
}

0 commit comments

Comments
 (0)