From 3b37867c8ebd94f7f96002a5a359c0c399443e1a Mon Sep 17 00:00:00 2001 From: Eddy Ashton Date: Tue, 19 Nov 2024 15:56:44 +0000 Subject: [PATCH] When joining, always retrieve the service's `subject_name` from the given cert (#6660) (cherry picked from commit 863681de672fab01db90b7d9d70d9ea713735b5e) # Conflicts: # CHANGELOG.md # python/pyproject.toml # src/node/identity.h --- CHANGELOG.md | 8 ++++++++ src/node/identity.h | 3 ++- tests/lts_compatibility.py | 40 +++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a63c2c31f6c..3f8aaddbed16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.0.10] + +[5.0.10]: https://github.com/microsoft/CCF/releases/tag/5.0.10 + +### Fixed + +- Services upgrading from 4.x to 5.x may accidentally change their service's subject name, resulting in cryptographic errors when verifying anything endorsed by the old subject name. The subject name field is now correctly populated and retained across joins, renewals, and disaster recoveries. + ## [5.0.9] [5.0.9]: https://github.com/microsoft/CCF/releases/tag/ccf-5.0.9 diff --git a/src/node/identity.h b/src/node/identity.h index e6754298e39e..0d31ed52a7e8 100644 --- a/src/node/identity.h +++ b/src/node/identity.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/crypto/curve.h" +#include "ccf/crypto/verifier.h" #include "crypto/certs.h" #include "crypto/openssl/key_pair.h" @@ -73,7 +74,7 @@ namespace ccf } ReplicatedNetworkIdentity(const NetworkIdentity& other) : - NetworkIdentity(other.subject_name) + NetworkIdentity(ccf::crypto::get_subject_name(other.cert)) { if (type != other.type) { diff --git a/tests/lts_compatibility.py b/tests/lts_compatibility.py index a34b56ecddf8..dd6b3c8d9477 100644 --- a/tests/lts_compatibility.py +++ b/tests/lts_compatibility.py @@ -18,6 +18,8 @@ from governance import test_all_nodes_cert_renewal, test_service_cert_renewal from infra.snp import IS_SNP from distutils.dir_util import copy_tree +from cryptography import x509 +from cryptography.hazmat.backends import default_backend from loguru import logger as LOG @@ -98,6 +100,7 @@ def test_new_service( binary_dir, library_dir, version, + expected_subject_name=None, ): if IS_SNP: LOG.info( @@ -149,6 +152,15 @@ def test_new_service( test_all_nodes_cert_renewal(network, args, valid_from=valid_from) test_service_cert_renewal(network, args, valid_from=valid_from) + if expected_subject_name: + LOG.info(f"Confirming subject name == {expected_subject_name}") + with primary.client() as c: + r = c.get("/node/network") + assert r.status_code == 200, r + cert_pem = r.body.json()["service_certificate"] + cert = x509.load_pem_x509_certificate(cert_pem.encode(), default_backend()) + assert cert.subject.rfc4514_string() == expected_subject_name, cert + LOG.info("Apply transactions to new nodes only") issue_activity_on_live_service(network, args) test_random_receipts(network, args, lts=True, log_capture=[]) @@ -206,6 +218,8 @@ def run_code_upgrade_from( set_js_args(args, from_install_path, to_install_path) + service_subject_name = "CN=LTS custom service name" + jwt_issuer = infra.jwt_issuer.JwtIssuer( "https://localhost", refresh_interval=args.jwt_key_refresh_interval_s ) @@ -225,7 +239,10 @@ def run_code_upgrade_from( kwargs["reconfiguration_type"] = "OneTransaction" network.start_and_open( - args, node_container_image=from_container_image, **kwargs + args, + node_container_image=from_container_image, + service_subject_name=service_subject_name, + **kwargs, ) old_nodes = network.get_joined_nodes() @@ -287,6 +304,26 @@ def run_code_upgrade_from( version == expected_version ), f"For node {node.local_node_id}, expect version {expected_version}, got {version}" + # Verify that either custom service_subject_name was applied, + # or that a default name is used + primary, _ = network.find_primary() + with primary.client() as c: + r = c.get("/node/network") + assert r.status_code == 200, r + cert_pem = r.body.json()["service_certificate"] + cert = x509.load_pem_x509_certificate( + cert_pem.encode(), default_backend() + ) + version = primary.version or args.ccf_version + if not infra.node.version_after(version, "ccf-5.0.0-dev14"): + service_subject_name = cert.subject.rfc4514_string() + LOG.info( + f"Custom subject name not supported on {version}, so falling back to default {service_subject_name}" + ) + else: + LOG.info(f"Custom subject name should be supported on {version}") + assert cert.subject.rfc4514_string() == service_subject_name, cert + LOG.info("Apply transactions to hybrid network, with primary as old node") issue_activity_on_live_service(network, args) @@ -371,6 +408,7 @@ def run_code_upgrade_from( to_binary_dir, to_library_dir, to_version, + service_subject_name, ) network.get_latest_ledger_public_state()