Skip to content

Commit 0a3ba2b

Browse files
authored
Remove UVM endorsement descriptor hardcoding at Start and Recovery (#6869)
1 parent cf87fb5 commit 0a3ba2b

File tree

5 files changed

+108
-6
lines changed

5 files changed

+108
-6
lines changed

.snpcc_canary

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,3 @@
33
O \ o | / |
44
/-xXx--//-----x=x--/-xXx--/---x-/--->>>--/
55
...
6-
/\/\d(-_-)b/\/\
7-
----vmpl----

CHANGELOG.md

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

1414
- `ccf.ledger`/`read_ledger.py` previously enforced too strict a condition on node membership when validating ledger files (#6849).
1515
- Restore low CPU usage on idle nodes, which had increased in dev20 (#6816).
16+
- Nodes in Start and Recovery modes no longer enforce specific UVM descriptors, and will instead derive one from UVM endorsements if found. The consortium must check that the value is acceptable, record their agreement by opening the network (#6869).
1617

1718
## [6.0.0-dev20]
1819

src/node/node_state.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,21 @@ namespace ccf
347347
{
348348
auto uvm_endorsements_raw = ccf::crypto::raw_from_b64(
349349
config.attestation.environment.uvm_endorsements.value());
350-
snp_uvm_endorsements =
351-
verify_uvm_endorsements(uvm_endorsements_raw, node_measurement);
350+
// A node at this stage does not have a notion of what UVM
351+
// descriptor is acceptable. That is decided either by the Joinee,
352+
// or by Consortium endorsing the Start or Recovery node. For that
353+
// reason, we extract an endorsement descriptor from the UVM
354+
// endorsements and make it available in the ledger's initial or
355+
// recovery transaction.
356+
snp_uvm_endorsements = verify_uvm_endorsements(
357+
uvm_endorsements_raw,
358+
node_measurement,
359+
{},
360+
false /* Do not check roots of trust */);
352361
quote_info.uvm_endorsements = uvm_endorsements_raw;
362+
LOG_INFO_FMT(
363+
"Successfully verified attested UVM endorsements: {}",
364+
snp_uvm_endorsements->to_str());
353365
}
354366
catch (const std::exception& e)
355367
{

src/node/uvm_endorsements.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ namespace ccf
2626
std::string svn;
2727

2828
bool operator==(const UVMEndorsements&) const = default;
29+
30+
inline std::string to_str()
31+
{
32+
return fmt::format("did: {}, feed: {}, svn: {}", did, feed, svn);
33+
}
2934
};
3035
DECLARE_JSON_TYPE(UVMEndorsements);
3136
DECLARE_JSON_REQUIRED_FIELDS(UVMEndorsements, did, feed, svn);
@@ -269,7 +274,8 @@ namespace ccf
269274
const std::vector<uint8_t>& uvm_endorsements_raw,
270275
const pal::PlatformAttestationMeasurement& uvm_measurement,
271276
const std::vector<UVMEndorsements>& uvm_roots_of_trust =
272-
default_uvm_roots_of_trust)
277+
default_uvm_roots_of_trust,
278+
bool enforce_uvm_roots_of_trust = true)
273279
{
274280
auto phdr = cose::decode_protected_header(uvm_endorsements_raw);
275281

@@ -368,7 +374,9 @@ namespace ccf
368374

369375
UVMEndorsements end{did, phdr.feed, payload.sevsnpvm_guest_svn};
370376

371-
if (!matches_uvm_roots_of_trust(end, uvm_roots_of_trust))
377+
if (
378+
enforce_uvm_roots_of_trust &&
379+
!matches_uvm_roots_of_trust(end, uvm_roots_of_trust))
372380
{
373381
throw std::logic_error(fmt::format(
374382
"UVM endorsements did {}, feed {}, svn {} "

tests/e2e_operations.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import infra.logging_app as app
88
import infra.e2e_args
99
import infra.network
10+
import infra.snp
1011
import ccf.ledger
1112
from ccf.tx_id import TxID
1213
import base64
@@ -959,6 +960,86 @@ def run_empty_ledger_dir_check(args):
959960
)
960961

961962

963+
def run_initial_uvm_descriptor_checks(args):
964+
with infra.network.network(
965+
args.nodes,
966+
args.binary_dir,
967+
args.debug_nodes,
968+
args.perf_nodes,
969+
pdb=args.pdb,
970+
) as network:
971+
LOG.info("Start a network and stop it")
972+
network.start_and_open(args)
973+
primary, _ = network.find_primary()
974+
old_common = infra.network.get_common_folder_name(args.workspace, args.label)
975+
snapshots_dir = network.get_committed_snapshots(primary)
976+
network.stop_all_nodes()
977+
LOG.info("Check that the a UVM descriptor is present")
978+
979+
ledger_dirs = primary.remote.ledger_paths()
980+
ledger = ccf.ledger.Ledger(ledger_dirs)
981+
first_chunk = next(iter(ledger))
982+
first_tx = next(first_chunk)
983+
tables = first_tx.get_public_domain().get_tables()
984+
endorsements = tables["public:ccf.gov.nodes.snp.uvm_endorsements"]
985+
assert len(endorsements) == 1, endorsements
986+
(key,) = endorsements.keys()
987+
assert key.startswith(b"did:x509:"), key
988+
LOG.info(f"Initial UVM endorsement found in ledger: {endorsements[key]}")
989+
990+
LOG.info("Start a recovery network and stop it")
991+
current_ledger_dir, committed_ledger_dirs = primary.get_ledger()
992+
recovered_network = infra.network.Network(
993+
args.nodes,
994+
args.binary_dir,
995+
args.debug_nodes,
996+
args.perf_nodes,
997+
existing_network=network,
998+
)
999+
1000+
args.previous_service_identity_file = os.path.join(
1001+
old_common, "service_cert.pem"
1002+
)
1003+
recovered_network.start_in_recovery(
1004+
args,
1005+
ledger_dir=current_ledger_dir,
1006+
committed_ledger_dirs=committed_ledger_dirs,
1007+
snapshots_dir=snapshots_dir,
1008+
)
1009+
recovered_primary, _ = recovered_network.find_primary()
1010+
LOG.info("Check that the UVM descriptor is present in the recovery tx")
1011+
recovery_seqno = None
1012+
with recovered_primary.client() as c:
1013+
r = c.get("/node/network").body.json()
1014+
recovery_seqno = int(r["current_service_create_txid"].split(".")[1])
1015+
network.stop_all_nodes()
1016+
ledger = ccf.ledger.Ledger(
1017+
recovered_primary.remote.ledger_paths(),
1018+
committed_only=False,
1019+
read_recovery_files=True,
1020+
)
1021+
for chunk in ledger:
1022+
_, chunk_end_seqno = chunk.get_seqnos()
1023+
if chunk_end_seqno < recovery_seqno:
1024+
continue
1025+
for tx in chunk:
1026+
tables = tx.get_public_domain().get_tables()
1027+
seqno = tx.get_public_domain().get_seqno()
1028+
if seqno < recovery_seqno:
1029+
continue
1030+
else:
1031+
tables = tx.get_public_domain().get_tables()
1032+
endorsements = tables["public:ccf.gov.nodes.snp.uvm_endorsements"]
1033+
assert len(endorsements) == 1, endorsements
1034+
(key,) = endorsements.keys()
1035+
assert key.startswith(b"did:x509:"), key
1036+
LOG.info(
1037+
f"Recovery UVM endorsement found in ledger: {endorsements[key]}"
1038+
)
1039+
return
1040+
assert False, "No UVM endorsement found in recovery ledger"
1041+
1042+
9621043
def run(args):
9631044
run_max_uncommitted_tx_count(args)
9641045
run_file_operations(args)
@@ -972,3 +1053,5 @@ def run(args):
9721053
run_cose_signatures_config_check(args)
9731054
run_late_mounted_ledger_check(args)
9741055
run_empty_ledger_dir_check(args)
1056+
if infra.snp.is_snp():
1057+
run_initial_uvm_descriptor_checks(args)

0 commit comments

Comments
 (0)