Skip to content

Commit

Permalink
Service cert subject name is configurable (#5993)
Browse files Browse the repository at this point in the history
  • Loading branch information
achamayou authored Mar 18, 2024
1 parent f60ac15 commit 68d5937
Show file tree
Hide file tree
Showing 19 changed files with 85 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .daily_canary
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
(- -) (= =) | Y & +--?
( V ) / . \ | +---=---'
/--x-m- /--n-n---xXx--/--yY------>>>----<<<>>]]{{}}---||-/\---..
2024!---
2024___
5 changes: 5 additions & 0 deletions doc/host_config_schema/cchost_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@
"description": "Initial validity period (days) for service certificate",
"minimum": 1
},
"service_subject_name": {
"type": "string",
"default": "CN=CCF Service",
"description": "Subject name to include in service certificate. Can only be set once on service start."
},
"members": {
"type": "array",
"items": {
Expand Down
2 changes: 2 additions & 0 deletions include/ccf/crypto/verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,6 @@ namespace crypto
const std::vector<uint8_t>& der);

crypto::Pem public_key_pem_from_cert(const std::vector<uint8_t>& der);

std::string get_subject_name(const Pem& cert);
}
2 changes: 2 additions & 0 deletions include/ccf/node/startup_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ struct StartupConfig : CCFConfig

// Only if starting or recovering
size_t initial_service_certificate_validity_days = 1;
std::string service_subject_name = "CN=CCF Service";

nlohmann::json service_data = nullptr;

nlohmann::json node_data = nullptr;
Expand Down
3 changes: 2 additions & 1 deletion samples/config/start_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"recovery_threshold": 1,
"maximum_node_certificate_validity_days": 365
},
"initial_service_certificate_validity_days": 1
"initial_service_certificate_validity_days": 1,
"service_subject_name": "CN=A Sample CCF Service"
}
},
"ledger": {
Expand Down
1 change: 1 addition & 0 deletions src/common/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ DECLARE_JSON_REQUIRED_FIELDS(
startup_host_time,
snapshot_tx_interval,
initial_service_certificate_validity_days,
service_subject_name,
service_data,
node_data,
start,
Expand Down
11 changes: 7 additions & 4 deletions src/crypto/openssl/key_pair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,14 +379,17 @@ namespace crypto
X509V3_set_ctx_nodb(&v3ctx);
X509V3_set_ctx(&v3ctx, icrt ? icrt : crt, NULL, csr, NULL, 0);

std::string constraints = "critical,CA:FALSE";
if (ca)
{
constraints = "critical,CA:TRUE,pathlen:0";
}

// Add basic constraints
X509_EXTENSION* ext = NULL;
OpenSSL::CHECKNULL(
ext = X509V3_EXT_conf_nid(
NULL,
&v3ctx,
NID_basic_constraints,
ca ? "critical,CA:TRUE,pathlen:0" : "critical,CA:FALSE"));
NULL, &v3ctx, NID_basic_constraints, constraints.c_str()));
OpenSSL::CHECK1(X509_add_ext(crt, ext, -1));
X509_EXTENSION_free(ext);

Expand Down
5 changes: 5 additions & 0 deletions src/crypto/verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ namespace crypto
{
return make_unique_verifier(der)->public_key_pem();
}

std::string get_subject_name(const Pem& cert)
{
return make_verifier(cert)->subject();
}
}
4 changes: 3 additions & 1 deletion src/host/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ namespace host
std::vector<std::string> constitution_files = {};
ccf::ServiceConfiguration service_configuration;
size_t initial_service_certificate_validity_days = 1;
std::string service_subject_name = "CN=CCF Service";

bool operator==(const Start&) const = default;
};
Expand Down Expand Up @@ -205,7 +206,8 @@ namespace host
DECLARE_JSON_OPTIONAL_FIELDS(
CCHostConfig::Command::Start,
service_configuration,
initial_service_certificate_validity_days);
initial_service_certificate_validity_days,
service_subject_name);

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CCHostConfig::Command::Join);
DECLARE_JSON_REQUIRED_FIELDS(CCHostConfig::Command::Join, target_rpc_address);
Expand Down
2 changes: 2 additions & 0 deletions src/host/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ int main(int argc, char** argv)
recovery_threshold;
startup_config.initial_service_certificate_validity_days =
config.command.start.initial_service_certificate_validity_days;
startup_config.service_subject_name =
config.command.start.service_subject_name;
LOG_INFO_FMT(
"Creating new node: new network (with {} initial member(s) and {} "
"member(s) required for recovery)",
Expand Down
4 changes: 2 additions & 2 deletions src/node/historical_queries_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ namespace ccf
auto ncv = crypto::make_unique_verifier(network_identity->cert);
auto endorsement = create_endorsed_cert(
hpubkey,
ReplicatedNetworkIdentity::subject_name,
crypto::get_subject_name(opt_psi->cert),
{},
ncv->validity_period(),
network_identity->priv_key,
network_identity->cert,
true);
true /* CA */);
service_endorsement_cache[hpubkey] = {endorsement};
receipt.service_endorsements = {endorsement};
}
Expand Down
38 changes: 12 additions & 26 deletions src/node/identity.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@ namespace ccf
{
crypto::Pem priv_key;
crypto::Pem cert;
std::optional<IdentityType> type;
std::optional<IdentityType> type = IdentityType::REPLICATED;
std::string subject_name = "CN=CCF Service";

bool operator==(const NetworkIdentity& other) const
{
return cert == other.cert && priv_key == other.priv_key &&
type == other.type;
type == other.type && subject_name == other.subject_name;
}

NetworkIdentity() : type(IdentityType::REPLICATED) {}
NetworkIdentity(IdentityType type) : type(type) {}
NetworkIdentity(const std::string& subject_name_) :
type(IdentityType::REPLICATED),
subject_name(subject_name_)
{}
NetworkIdentity() = default;

virtual crypto::Pem issue_certificate(
const std::string& valid_from, size_t validity_period_days)
Expand All @@ -47,15 +51,14 @@ namespace ccf
class ReplicatedNetworkIdentity : public NetworkIdentity
{
public:
static constexpr auto subject_name = "CN=CCF Network";

ReplicatedNetworkIdentity() : NetworkIdentity(IdentityType::REPLICATED) {}
ReplicatedNetworkIdentity() = default;

ReplicatedNetworkIdentity(
const std::string& subject_name_,
crypto::CurveID curve_id,
const std::string& valid_from,
size_t validity_period_days) :
NetworkIdentity(IdentityType::REPLICATED)
NetworkIdentity(subject_name_)
{
auto identity_key_pair =
std::make_shared<crypto::KeyPair_OpenSSL>(curve_id);
Expand All @@ -70,7 +73,7 @@ namespace ccf
}

ReplicatedNetworkIdentity(const NetworkIdentity& other) :
NetworkIdentity(IdentityType::REPLICATED)
NetworkIdentity(other.subject_name)
{
if (type != other.type)
{
Expand Down Expand Up @@ -104,21 +107,4 @@ namespace ccf
OPENSSL_cleanse(priv_key.data(), priv_key.size());
}
};

class SplitNetworkIdentity : public NetworkIdentity
{
public:
SplitNetworkIdentity() : NetworkIdentity(IdentityType::SPLIT) {}

SplitNetworkIdentity(const NetworkIdentity& other) :
NetworkIdentity(IdentityType::SPLIT)
{
if (type != other.type)
{
throw std::runtime_error("invalid identity type conversion");
}
priv_key = {};
cert = other.cert;
}
};
}
5 changes: 5 additions & 0 deletions src/node/node_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ namespace ccf
case StartType::Start:
{
network.identity = std::make_unique<ReplicatedNetworkIdentity>(
config.service_subject_name,
curve_id,
config.startup_host_time,
config.initial_service_certificate_validity_days);
Expand Down Expand Up @@ -525,7 +526,11 @@ namespace ccf
"identity");
}

crypto::Pem previous_service_identity_cert(
config.recover.previous_service_identity.value());

network.identity = std::make_unique<ReplicatedNetworkIdentity>(
crypto::get_subject_name(previous_service_identity_cert),
curve_id,
config.startup_host_time,
config.initial_service_certificate_validity_days);
Expand Down
4 changes: 1 addition & 3 deletions src/node/rpc/serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@ namespace ccf
{ccf::IdentityType::SPLIT, "Split"}})
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(NetworkIdentity)
DECLARE_JSON_REQUIRED_FIELDS(NetworkIdentity, cert, priv_key)
DECLARE_JSON_OPTIONAL_FIELDS(NetworkIdentity, type)
DECLARE_JSON_OPTIONAL_FIELDS(NetworkIdentity, type, subject_name)
DECLARE_JSON_TYPE_WITH_BASE(ReplicatedNetworkIdentity, NetworkIdentity)
DECLARE_JSON_TYPE_WITH_BASE(SplitNetworkIdentity, NetworkIdentity)
DECLARE_JSON_REQUIRED_FIELDS(SplitNetworkIdentity, cert, type)

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(
JoinNetworkNodeToNode::Out::NetworkInfo)
Expand Down
5 changes: 4 additions & 1 deletion src/node/rpc/test/frontend_test_infra.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ std::unique_ptr<ccf::NetworkIdentity> make_test_network_ident()
const auto valid_from =
ds::to_x509_time_string(std::chrono::system_clock::now() - 24h);
return std::make_unique<ReplicatedNetworkIdentity>(
crypto::service_identity_curve_choice, valid_from, 2);
"CN=CCF test network",
crypto::service_identity_curve_choice,
valid_from,
2);
}

void init_network(NetworkState& network)
Expand Down
3 changes: 2 additions & 1 deletion tests/config.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"maximum_node_certificate_validity_days": {{ maximum_node_certificate_validity_days }},
"maximum_service_certificate_validity_days": {{ maximum_service_certificate_validity_days }}
},
"initial_service_certificate_validity_days": {{ initial_service_cert_validity_days }}
"initial_service_certificate_validity_days": {{ initial_service_cert_validity_days }},
"service_subject_name": {{ service_subject_name|tojson }}
},
"join":
{
Expand Down
26 changes: 26 additions & 0 deletions tests/e2e_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import time
import http
import infra.snp as snp
from cryptography import x509
from cryptography.hazmat.backends import default_backend

from loguru import logger as LOG

Expand Down Expand Up @@ -564,6 +566,29 @@ def run_max_uncommitted_tx_count(args):
assert r.status_code == http.HTTPStatus.OK.value, r


def run_service_subject_name_check(args):
with infra.network.network(
args.nodes,
args.binary_dir,
args.debug_nodes,
args.perf_nodes,
pdb=args.pdb,
) as network:
network.start_and_open(args, service_subject_name="CN=This test service")
# Check service_cert.pem
with open(network.cert_path, "rb") as cert_file:
cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())
assert cert.subject.rfc4514_string() == "CN=This test service", cert
# Check /node/service endpoint
primary, _ = network.find_primary()
with primary.client() as c:
r = c.get("/node/network")
assert r.status_code == http.HTTPStatus.OK.value, r
cert_pem = r.body.json()["service_certificate"]
cert = x509.load_pem_x509_certificate(cert_pem.encode(), default_backend())
assert cert.subject.rfc4514_string() == "CN=This test service", cert


def run(args):
run_max_uncommitted_tx_count(args)
run_file_operations(args)
Expand All @@ -573,3 +598,4 @@ def run(args):
run_pid_file_check(args)
run_preopen_readiness_check(args)
run_sighup_check(args)
run_service_subject_name_check(args)
2 changes: 2 additions & 0 deletions tests/infra/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ def __init__(
max_uncommitted_tx_count=0,
snp_security_policy_file=None,
snp_uvm_endorsements_file=None,
service_subject_name="CN=CCF Test Service",
**kwargs,
):
"""
Expand Down Expand Up @@ -813,6 +814,7 @@ def __init__(
max_uncommitted_tx_count=max_uncommitted_tx_count,
snp_security_policy_file=snp_security_policy_file,
snp_uvm_endorsements_file=snp_uvm_endorsements_file,
service_subject_name=service_subject_name,
**kwargs,
)

Expand Down
2 changes: 1 addition & 1 deletion tests/tls_report.csv
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"banner_reverseproxy","","INFO","--","","CWE-200"
"banner_server","","INFO","No Server banner line in header, interesting!","",""
"cert","","INFO","----------","",""
"cert_caIssuers","","INFO","CCF Network","",""
"cert_caIssuers","","INFO","CCF Test Service","",""
"cert_certificatePolicies_EV","","INFO","no","",""
"cert_chain_of_trust","","CRITICAL","failed (chain incomplete).","",""
"cert_commonName","","OK","CCF Node","",""
Expand Down

0 comments on commit 68d5937

Please sign in to comment.