Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service identity endorsements to historical receipts #3679

Merged
merged 38 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
33b8f35
Record service identity endorsements upon recovery
Mar 16, 2022
f2977c6
Add failing test of receipts across recoveries
Mar 16, 2022
8278af3
Add direct endorsements of previous service identities
Mar 18, 2022
f630dc6
Add missing argument
Mar 18, 2022
66cba83
Disable debug code
Mar 18, 2022
1f3b69d
Formatting
Mar 18, 2022
0bcd5f2
More
Mar 22, 2022
caa7a39
Disable debug code
Mar 22, 2022
b53f8dd
Proper intermediate certs
Mar 23, 2022
ac387b8
Version numbers, changelog
Mar 23, 2022
24355a6
Add missing import
Mar 24, 2022
cdcb0f5
Fix bad merge
Mar 24, 2022
7b89f06
Error instead of ledger walk back into 1.x regions
Mar 24, 2022
f80e63e
Keep OpenSSL service endorsement checks
Mar 24, 2022
1c1e7cf
Add note about issuer-signed CSRs
Mar 24, 2022
5a1bf6e
Add certifiate signer enum
Mar 24, 2022
ec627e8
-debug +formatting
Mar 24, 2022
ee207e8
Update expired test ledger to new service table entries
Mar 24, 2022
dce7dfe
Docs and moved endorsements checks to fit in better with docs
Mar 24, 2022
ffef4cc
Formatting
Mar 24, 2022
f143691
Get network identity subsystem more gracefully
Mar 24, 2022
5df1331
Add ccf::historical::adapter_v3
Mar 24, 2022
db78e10
Fix for tests that don't install the network identity subsystem
Mar 24, 2022
2fff035
Formatting
Mar 24, 2022
8aac04f
Fix network identity subsystem problems and make it private
Mar 25, 2022
2b7afb1
Formatting
Mar 25, 2022
9f45e47
Formatting
Mar 25, 2022
4371399
Remove leftover endorsement check flag
Mar 25, 2022
9f46c00
Trigger daily
Mar 25, 2022
83223e9
Allow LTS compat test to skip endorsement checks
Mar 25, 2022
775e208
Fix
Mar 25, 2022
ce03fad
Better error message
Mar 28, 2022
5b0a2ab
Soft error
Mar 28, 2022
1adf228
Catch 1.x errors
Mar 28, 2022
87188b0
Update CHANGELOG.md
Mar 28, 2022
87f9cf5
Nicer `or []`
Mar 28, 2022
5d56c6a
No errors for missing endorsements
Mar 28, 2022
3c0cd19
None -> []
Mar 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .daily_canary
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Run!!!!
There, he moved!
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- The `enclave::` namespace has been removed, and all types which were under it are now under `ccf::`. This will affect any apps using `enclave::RpcContext`, which should be replaced with `ccf::RpcContext` (#3664).
- HTTP parsing errors are now recorded per-interface and returned by `GET /node/metrics` (#3671).
- The `kv::Store` type is no longer visible to application code, and is replaced by a simpler `kv::ReadOnlyStore`. This is the interface given to historical queries to access historical state and enforces read-only access, without exposing internal implementation details of the store. This should have no impact on JS apps, but C++ apps will need to replace calls to `store->current_txid()` with calls to `store->get_txid()`, and `store->create_tx()` to `store->create_read_only_tx()`.
- Receipts now come with service endorsements of previous service identities after recoveries (#3679). See `verify_receipt` in `e2e_logging.py` for an example of how to verify the resulting certificate chain. This functionality is introduced in `ccf::historical::adapter_v3`.

## [2.0.0-rc4]

Expand Down
14 changes: 13 additions & 1 deletion doc/schemas/app_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@
"pattern": "^[a-f0-9]{64}$",
"type": "string"
},
"Pem": {
"type": "string"
},
"Pem_array": {
"items": {
"$ref": "#/components/schemas/Pem"
},
"type": "array"
},
"Receipt": {
"properties": {
"cert": {
Expand All @@ -221,6 +230,9 @@
"root": {
"$ref": "#/components/schemas/string"
},
"service_endorsements": {
"$ref": "#/components/schemas/Pem_array"
},
"signature": {
"$ref": "#/components/schemas/string"
}
Expand Down Expand Up @@ -310,7 +322,7 @@
"info": {
"description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.",
"title": "CCF Sample Logging App",
"version": "1.9.0"
"version": "1.9.1"
},
"openapi": "3.0.0",
"paths": {
Expand Down
11 changes: 10 additions & 1 deletion doc/schemas/gov_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@
"Pem": {
"type": "string"
},
"Pem_array": {
"items": {
"$ref": "#/components/schemas/Pem"
},
"type": "array"
},
"Proposal": {
"properties": {
"actions": {
Expand Down Expand Up @@ -354,6 +360,9 @@
"root": {
"$ref": "#/components/schemas/string"
},
"service_endorsements": {
"$ref": "#/components/schemas/Pem_array"
},
"signature": {
"$ref": "#/components/schemas/string"
}
Expand Down Expand Up @@ -472,7 +481,7 @@
"info": {
"description": "This API is used to submit and query proposals which affect CCF's public governance tables.",
"title": "CCF Governance API",
"version": "2.7.0"
"version": "2.7.1"
},
"openapi": "3.0.0",
"paths": {
Expand Down
11 changes: 10 additions & 1 deletion doc/schemas/node_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,12 @@
"Pem": {
"type": "string"
},
"Pem_array": {
"items": {
"$ref": "#/components/schemas/Pem"
},
"type": "array"
},
"Quote": {
"properties": {
"endorsements": {
Expand Down Expand Up @@ -556,6 +562,9 @@
"root": {
"$ref": "#/components/schemas/string"
},
"service_endorsements": {
"$ref": "#/components/schemas/Pem_array"
},
"signature": {
"$ref": "#/components/schemas/string"
}
Expand Down Expand Up @@ -773,7 +782,7 @@
"info": {
"description": "This API provides public, uncredentialed access to service and node state.",
"title": "CCF Public Node API",
"version": "2.16.0"
"version": "2.16.1"
},
"openapi": "3.0.0",
"paths": {
Expand Down
17 changes: 16 additions & 1 deletion doc/use_apps/verify_tx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ This means that the request may return ``202 Accepted`` at first, with a suggest
{'right': '8e238d95767e6ffe4b20e1a5e93dd7b926cbd86caa83698584a16ad2dd7d60b8'},
{'left': 'd4717996ae906cdce0ac47257a4a9445c58474c2f40811e575f804506e5fee9f'},
{'left': 'c1c206c4670bd2adee821013695d593f5983ca0994ae74630528da5fb6642205'}],
'service_endorsements': [ '-----BEGIN CERTIFICATE-----'
'MIIBtTCCATugAwIBAgIRAN37fxGnWYNVLZn8nM8iBP8wCgYIKoZIzj0EAwMwFjEU\n'
'MBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjIwMzIzMTMxMDA2WhcNMjIwMzI0MTMx\n'
'MDA1WjAWMRQwEgYDVQQDDAtDQ0YgTmV0d29yazB2MBAGByqGSM49AgEGBSuBBAAi\n'
'A2IABBErIfAEVg2Uw+iBPV9kEcpQw8NcoZWHmj4boHf7VVd6yCwRl+X/wOaOudca\n'
'CqMMcwrt4Bb7n11RbsRwU04B7fG907MelICFHiPZjU/XMK5HEsSEZWowVtNwOLDo\n'
'l5cN6aNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQU4n5gHhHFnYZc3nwxKRggl8YB\n'
'qdgwHwYDVR0jBBgwFoAUcAvR3F5YSUvPPGcAxrvh2Z5ump8wCgYIKoZIzj0EAwMD\n'
'aAAwZQIxAMeRoXo9FDzr51qkiD4Ws0Y+KZT06MFHcCg47TMDSGvnGrwL3DcIjGs7\n'
'TTwJJQjbWAIwS9AqOJP24sN6jzXOTd6RokeF/MTGJbQAihzgTbZia7EKM8s/0yDB\n'
'0QYtrfMjtPOx\n'
'-----END CERTIFICATE-----\n'
],
'signature': 'MGQCMHrnwS123oHqUKuQRPsQ+gk6WVutixeOvxcXX79InBgPOxJCoScCOlBnK4UYyLzangIwW9k7IZkMgG076qVv5zcx7OuKb7bKyii1yP1rcakeGVvVMwISeE+Fr3BnFfPD66Df'}
`cert` contains the certificate of the signing node, endorsed by the service identity. `node_id` is the node's ID inside CCF, a digest of its public key.
Expand All @@ -97,6 +110,8 @@ The proof is empty, and the ``leaf`` field is set to the value being signed, whi
This allows writing verification code that handles both regular and signature receipts similarly, but it is worth noting that the 'leaf' value for signatures is _not_
the digest of the signature transaction itself.

From version 2.0, CCF also includes endorsement certificates for previous service identities, by the current service identity, in `service_endorsements`. Thus, after at least one recovery, the endorsement check now takes the form of a certificate chain verification instead of a single endorsement check.

Receipt Verification
--------------------

Expand All @@ -106,7 +121,7 @@ Verifying a receipt consists of the following steps:
2. If the receipt contains ``leaf_components``, digest the concatenation ``write_set_digest + commit_evidence_digest + claims_digest`` to produce ``leaf``.
3. Combine ``leaf`` with the successive elements in ``proof`` to calculate the value of ``root``. See :py:func:`ccf.receipt.root` for a reference implementation.
4. Verify ``signature`` over the ``root`` using the certificate of the node identified by ``node_id`` and ``cert``. See :py:func:`ccf.receipt.verify` for a reference implementation.
5. Check that the certificate ``cert`` of ``node_id`` used to sign the receipt is endorsed by the CCF network. See :py:func:`ccf.receipt.check_endorsement` for a reference implementation.
5. Check that the certificate ``cert`` of ``node_id`` used to sign the receipt is endorsed by the CCF network. See :py:func:`ccf.receipt.check_endorsements` for a reference implementation.

Note that since a receipt is a committment by a service to a transaction, a verifier must know the service identity, and provide it as an input to step 5.

Expand Down
23 changes: 21 additions & 2 deletions include/ccf/crypto/key_pair.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,38 @@ namespace crypto

virtual Pem create_csr(
const std::string& subject_name,
const std::vector<SubjectAltName>& subject_alt_names) const = 0;
const std::vector<SubjectAltName>& subject_alt_names,
const std::optional<Pem>& public_key = std::nullopt) const = 0;

Pem create_csr(const std::string& subject_name) const
{
return create_csr(subject_name, {});
}

// Note about the signed_by_issuer parameter to sign_csr: when issuing a new
// certificate for an old subject, which does not exist anymore, we cannot
// sign the CSR with that old subject's private key. Instead, the issuer
// signs the CSR itself, which is slightly unusal. Instead, we could also
// ask the subject to produce a CSR right after it becomes alive and keep it
// around until we need it, but those complications are not stricly
// necessary. In our case, we use this to re-endorse previous service
// identities, which are self-signed, and replace them with new endorsements
// by the current service identity (which doesn't have the private key of
// previous ones).

enum class Signer
{
SUBJECT = 0,
ISSUER = 1
};

virtual Pem sign_csr(
const Pem& issuer_cert,
const Pem& signing_request,
const std::string& valid_from,
const std::string& valid_to,
bool ca = false) const = 0;
bool ca = false,
Signer signer = Signer::SUBJECT) const = 0;

Pem self_sign(
const std::string& name,
Expand Down
19 changes: 18 additions & 1 deletion include/ccf/historical_queries_adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "ccf/ccf_deprecated.h"
#include "ccf/endpoint_context.h"
#include "ccf/historical_queries_interface.h"
#include "ccf/node_context.h"
#include "ccf/tx_id.h"
#include "ccf/tx_status.h"

Expand Down Expand Up @@ -49,9 +50,25 @@ namespace ccf::historical
ccf::SeqNo seqno,
std::string& error_reason);

ccf::endpoints::EndpointFunction adapter_v3(
const HandleHistoricalQuery& f,
ccfapp::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const TxIDExtractor& extractor = txid_from_header);

/// @cond
// Doxygen cannot parse these declarations; some combination of a macro,
// attribute syntax, and namespaced types results in the following warning
// (treated as error):
// Found ';' while parsing initializer list! (doxygen could be confused by a
// macro call without semicolon)
// Use label-less cond to unconditionally exclude this block from parsing
// until the declarations are removed are removed.
CCF_DEPRECATED(
"Will be removed in 3.0, switch to ccf::historical::adapter_v3")
ccf::endpoints::EndpointFunction adapter_v2(
const HandleHistoricalQuery& f,
AbstractStateCache& state_cache,
ccfapp::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const TxIDExtractor& extractor = txid_from_header);

Expand Down
8 changes: 7 additions & 1 deletion include/ccf/receipt.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

#pragma once

#include "ccf/crypto/pem.h"
#include "ccf/ds/json.h"
#include "ccf/entity_id.h"

#include <optional>

namespace ccf
{
struct Receipt
Expand Down Expand Up @@ -51,6 +54,8 @@ namespace ccf
std::optional<std::string> leaf = std::nullopt;
/// Leaf components in transactions emitted by 2.x networks.
std::optional<LeafComponents> leaf_components = std::nullopt;

wintersteiger marked this conversation as resolved.
Show resolved Hide resolved
std::optional<std::vector<crypto::Pem>> service_endorsements = std::nullopt;
};

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Receipt::Element)
Expand All @@ -64,5 +69,6 @@ namespace ccf

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Receipt)
DECLARE_JSON_REQUIRED_FIELDS(Receipt, signature, proof, node_id)
DECLARE_JSON_OPTIONAL_FIELDS(Receipt, root, cert, leaf, leaf_components)
DECLARE_JSON_OPTIONAL_FIELDS(
Receipt, root, cert, leaf, leaf_components, service_endorsements)
}
11 changes: 6 additions & 5 deletions include/ccf/service/tables/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/crypto/pem.h"
#include "ccf/ds/json.h"
#include "ccf/service/map.h"

Expand All @@ -27,13 +28,13 @@ namespace ccf
/// x.509 Service Certificate, as a PEM string
crypto::Pem cert;
/// Status of the service
ServiceStatus status;
/// Previous service identity, before the last recovery
std::optional<crypto::Pem> previous_service_identity = std::nullopt;
ServiceStatus status = ServiceStatus::OPENING;
/// Version of previous service identity (before the last recovery)
std::optional<kv::Version> previous_service_identity_version = std::nullopt;
wintersteiger marked this conversation as resolved.
Show resolved Hide resolved
};
DECLARE_JSON_TYPE(ServiceInfo);
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(ServiceInfo);
DECLARE_JSON_REQUIRED_FIELDS(ServiceInfo, cert, status);
DECLARE_JSON_OPTIONAL_FIELDS(ServiceInfo, previous_service_identity);
DECLARE_JSON_OPTIONAL_FIELDS(ServiceInfo, previous_service_identity_version);

// As there is only one service active at a given time, it is stored in single
// Value in the KV
Expand Down
10 changes: 10 additions & 0 deletions python/ccf/receipt.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,13 @@ def check_endorsement(endorsee: Certificate, endorser: Certificate):
endorser_pk.verify(
endorsee.signature, digest, ec.ECDSA(utils.Prehashed(digest_algo))
)


def check_endorsements(
node_cert: Certificate, service_cert: Certificate, endorsements: List[Certificate]
):
cert_i = node_cert
for endorsement in endorsements:
check_endorsement(cert_i, endorsement)
cert_i = endorsement
check_endorsement(cert_i, service_cert)
17 changes: 6 additions & 11 deletions samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ namespace loggingapp
"This CCF sample app implements a simple logging application, securely "
"recording messages at client-specified IDs. It demonstrates most of "
"the features available to CCF apps.";
openapi_info.document_version = "1.9.0";
openapi_info.document_version = "1.9.1";

index_per_public_key = std::make_shared<RecordsIndexingStrategy>(
PUBLIC_RECORDS, context, 10000, 20);
Expand Down Expand Up @@ -842,8 +842,7 @@ namespace loggingapp
make_endpoint(
"/log/private/historical",
HTTP_GET,
ccf::historical::adapter_v2(
get_historical, context.get_historical_state(), is_tx_committed),
ccf::historical::adapter_v3(get_historical, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetHistorical::Out>()
.add_query_parameter<size_t>("id")
Expand Down Expand Up @@ -896,10 +895,8 @@ namespace loggingapp
make_endpoint(
"/log/private/historical_receipt",
HTTP_GET,
ccf::historical::adapter_v2(
get_historical_with_receipt,
context.get_historical_state(),
is_tx_committed),
ccf::historical::adapter_v3(
get_historical_with_receipt, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetReceipt::Out>()
.add_query_parameter<size_t>("id")
Expand Down Expand Up @@ -956,10 +953,8 @@ namespace loggingapp
make_endpoint(
"/log/public/historical_receipt",
HTTP_GET,
ccf::historical::adapter_v2(
get_historical_with_receipt_and_claims,
context.get_historical_state(),
is_tx_committed),
ccf::historical::adapter_v3(
get_historical_with_receipt_and_claims, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetReceipt::Out>()
.add_query_parameter<size_t>("id")
Expand Down
4 changes: 2 additions & 2 deletions src/apps/js_generic/js_generic_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ namespace ccfapp
consensus, view, seqno, error_reason);
};

ccf::historical::adapter_v2(
ccf::historical::adapter_v3(
[this, endpoint](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
Expand All @@ -255,7 +255,7 @@ namespace ccfapp
assert(receipt);
do_execute_request(endpoint, endpoint_ctx, &tx, tx_id, receipt);
},
context.get_historical_state(),
context,
is_tx_committed)(endpoint_ctx);
}
else
Expand Down
4 changes: 2 additions & 2 deletions src/apps/js_v8/js_v8_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ namespace ccfapp
consensus, view, seqno, error_reason);
};

ccf::historical::adapter_v2(
ccf::historical::adapter_v3(
[this, endpoint_def](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
do_execute_request(endpoint_def, endpoint_ctx, state);
},
context.get_historical_state(),
context,
is_tx_committed)(endpoint_ctx);
}
else
Expand Down
19 changes: 19 additions & 0 deletions src/crypto/certs.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,23 @@ namespace crypto
issuer_private_key,
issuer_cert);
}

static Pem create_endorsed_cert(
const Pem& public_key,
const std::string& subject_name,
const std::vector<SubjectAltName>& subject_alt_names,
const std::string& valid_from,
size_t validity_period_days,
const Pem& issuer_private_key,
const Pem& issuer_cert,
bool ca = false)
{
auto issuer_key_pair = make_key_pair(issuer_private_key);
auto csr =
issuer_key_pair->create_csr(subject_name, subject_alt_names, public_key);
auto valid_to =
compute_cert_valid_to_string(valid_from, validity_period_days);
return issuer_key_pair->sign_csr(
issuer_cert, csr, valid_from, valid_to, ca, KeyPair::Signer::ISSUER);
}
}
Loading