Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
73a4e28
WIP
Apr 30, 2020
93d38fc
This works (but it is not possible to check that a recovery share is …
Apr 30, 2020
3a2a24b
Merge remote-tracking branch 'upstream/master' into recovery_share_cli
Apr 30, 2020
c7bc148
WIP
May 5, 2020
7f6b594
Add table for submitted shares
May 18, 2020
0a7ed52
Add e2e test for recoveries
May 19, 2020
d724db6
Encrypt submitted shares
May 20, 2020
c3d463b
Fix issue with knowing when to recover from after primary changes
May 20, 2020
1e22136
Some extra testing
May 20, 2020
a90a315
Cleanup
May 20, 2020
8e3db39
More cleanup
May 21, 2020
212c1c8
Change schema of submitted table
May 21, 2020
8560a2c
Refactoring for better unit testing
May 21, 2020
bfa1a3c
Membervoting test works again
May 22, 2020
ee62eef
Even better
May 22, 2020
b5615ae
Frontend tests fix
May 22, 2020
c35575b
Unit test for bogus recovery shares
May 22, 2020
0e5981e
First cleanup before PR
May 22, 2020
f1b41c5
Better with three nodes
May 22, 2020
f1a4330
Merge remote-tracking branch 'upstream/master' into resilient_recovery
May 22, 2020
60938a6
Merge conflicts
May 22, 2020
c59f249
Finding a primary may take a long time when recovering the public ledger
May 22, 2020
66c2e48
Fix before PR
May 22, 2020
02d5423
Merge branch 'master' into resilient_recovery
jumaffre May 22, 2020
e6a7a32
Remove TODO
May 24, 2020
8cb46a2
Merge branch 'resilient_recovery' of github.com:jumaffre/CCF into res…
May 24, 2020
648aa4e
Merge branch 'resilient_recovery' into recovery_share_cli
May 25, 2020
c2364a7
WIP
May 26, 2020
e51627d
Merge remote-tracking branch 'upstream/master'
May 26, 2020
5fc2182
WIP
May 27, 2020
d56ee88
Merge remote-tracking branch 'upstream/master' into recovery_share_cli
Jun 15, 2020
8a80de7
Cleanup and docs
Jun 15, 2020
41ea01a
Cleanup
Jun 15, 2020
515869c
Check for commit of submitted share
Jun 16, 2020
95b393b
Schema, fix build
Jun 16, 2020
1e15320
Merge branch 'master' into recovery_share_cli
jumaffre Jun 16, 2020
e6361e0
Submit share from JSON to Text
Jun 16, 2020
defc04e
Remove global commit
Jun 16, 2020
3a2adbd
Cleanup docs
Jun 16, 2020
91a45b9
mktemp
Jun 16, 2020
9024998
Schema
Jun 16, 2020
ecb788c
Merge branch 'master' into recovery_share_cli
jumaffre Jun 17, 2020
5c69a2e
Merge branch 'master' into recovery_share_cli
achamayou Jun 17, 2020
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
5 changes: 3 additions & 2 deletions cmake/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ add_custom_command(

# Copy utilities from tests directory
set(CCF_UTILITIES tests.sh keygenerator.sh cimetrics_env.sh
upload_pico_metrics.py scurl.sh
upload_pico_metrics.py scurl.sh submit_recovery_share.sh
)
foreach(UTILITY ${CCF_UTILITIES})
configure_file(
Expand All @@ -101,7 +101,8 @@ endforeach()

# Install specific utilities
install(PROGRAMS ${CCF_DIR}/tests/scurl.sh ${CCF_DIR}/tests/keygenerator.sh
${CCF_DIR}/tests/sgxinfo.sh DESTINATION bin
${CCF_DIR}/tests/sgxinfo.sh
${CCF_DIR}/tests/submit_recovery_share.sh DESTINATION bin
)

# Install getting_started scripts for VM creation and setup
Expand Down
41 changes: 20 additions & 21 deletions doc/members/accept_recovery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,27 @@ To restore private transactions and complete the recovery procedure, members sho

.. note:: The members who submit their recovery shares do not necessarily have to be the members who previously accepted the recovery.

First, members should retrieve their encrypted recovery shares via the ``recovery_share`` RPC [#recovery_share]_.
The recovery share retrieval, decryption and submission steps are conveniently performed by the ``submit_recovery_share.sh`` script as follows:

.. code-block:: bash

$ curl https://<ccf-node-address>/members/recovery_share -X GET --cacert network_cert --key member1_privk --cert member1_cert -H "content-type: application/json"
$ ./submit_recovery_share.sh --rpc-address <ccf-node-address> --member-enc-privk member0_enc_privk.pem --network-enc-pubk network_enc_pubk --cert member0_cert
--key member0_privk --cacert network_cert
HTTP/1.1 200 OK
content-type: text/plain
x-ccf-tx-seqno: 28
x-ccf-tx-view: 4
1/2 recovery shares successfully submitted.

Then, members should decrypt their shares using their private encryption key and the `previous` network public encryption key (output by the first node of the now-defunct service via the ``network-enc-pubk-file`` :ref:`command line option <operators/start_network:Starting the First Node>`) using `NaCl's public-key authenticated encryption <https://nacl.cr.yp.to/box.html>`_.
$ ./submit_recovery_share.sh --rpc-address <ccf-node-address> --member-enc-privk member1_enc_privk.pem --network-enc-pubk network_enc_pubk --cert member1_cert
--key member1_privk --cacert network_cert
HTTP/1.1 200 OK
content-type: text/plain
x-ccf-tx-seqno: 30
x-ccf-tx-view: 4
2/2 recovery shares successfully submitted. End of recovery procedure initiated.

Finally, members should submit their decrypted share to CCF via the ``recovery_share/submit`` RPC:

.. code-block:: bash

$ cat submit_recovery_share.json
{"recovery_share": [<recovery_share_bytes>]}

$ curl https://<ccf-node-address>/members/recovery_share/submit -X POST --data-binary @submit_recovery_share.json --cacert network_cert --key member1_privk --cert member1_cert -H "content-type: application/json"
false

$ curl https://<ccf-node-address>/members/recovery_share/submit -X POST --data-binary @submit_recovery_share.json --cacert network_cert --key member2_privk --cert member2_cert -H "content-type: application/json"
true

When the recovery threshold is reached, the ``recovery_share/submit`` RPC returns ``true``. At this point, the private recovery procedure is started and the private ledger is being recovered.
When the recovery threshold is reached, the ``recovery_share/submit`` RPC returns that the end of the recovery procedure is initiated and the private ledger is now being recovered.

.. note:: While all nodes are recovering the private ledger, no new transaction can be executed by the network.

Expand Down Expand Up @@ -91,16 +90,16 @@ Summary Diagram
Node 2-->>Member 1: State: ACCEPTED
Note over Node 2, Node 3: accept_recovery proposal completes. Service is ready to accept recovery shares.

Member 0->>+Node 2: recovery_share
Member 0->>+Node 2: GET recovery_share
Node 2-->>Member 0: Encrypted recovery share for Member 0
Note over Member 0: Decrypts recovery share
Member 0->>+Node 2: recovery_share/submit: {"recovery_share": ...}
Member 0->>+Node 2: POST recovery_share/submit: {"recovery_share": ...}
Node 2-->>Member 0: False

Member 1->>+Node 2: recovery_share
Member 1->>+Node 2: GET recovery_share
Node 2-->>Member 1: Encrypted recovery share for Member 1
Note over Member 1: Decrypts recovery share
Member 1->>+Node 2: recovery_share/submit: {"recovery_share": ...}
Member 1->>+Node 2: POST recovery_share/submit: {"recovery_share": ...}
Node 2-->>Member 1: True

Note over Node 2, Node 3: Reading Private Ledger...
Expand Down
8 changes: 4 additions & 4 deletions doc/members/adding_member.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ The ``keygenerator.sh`` script can be used to generate the member’s certificat

.. code-block:: bash

$ keygenerator.sh --name=member_name --gen-enc-key
$ keygenerator.sh --name member_name --gen-enc-key
-- Generating identity private key and certificate for participant "member_name"...
Identity curve: secp384r1
Identity private key generated at: member_name_privk.pem
Identity certificate generated at: member_name_cert.pem (to be registered in CCF)
-- Generating encryption key pair for participant "member_name"...
Encryption private key generated at: member_name_enc_priv.pem
Encryption public key generated at: member_name_enc_pub.pem (to be registered in CCF)
Encryption private key generated at: member_name_enc_privk.pem
Encryption public key generated at: member_name_enc_pubk.pem (to be registered in CCF)

The member’s private keys (e.g. ``member_name_privk.pem`` and ``member_name_enc_priv.pem``) should be stored on a trusted device while the certificate (e.g. ``member_name_cert.pem``) and public encryption key (e.g. ``member_name_enc_pub.pem``) should be registered in CCF by members.
The member’s private keys (e.g. ``member_name_privk.pem`` and ``member_name_enc_privk.pem``) should be stored on a trusted device while the certificate (e.g. ``member_name_cert.pem``) and public encryption key (e.g. ``member_name_enc_pubk.pem``) should be registered in CCF by members.

.. note:: See :ref:`developers/cryptography:Algorithms and Curves` for the list of supported cryptographic curves for member identity.

Expand Down
8 changes: 5 additions & 3 deletions doc/operators/start_network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ CCF nodes can be started by using IP Addresses (both IPv4 and IPv6 are supported

When starting up, the node generates its own key pair and outputs the certificate associated with its public key at the location specified by ``--node-cert-file``. The certificate of the freshly-created CCF network is also output at the location specified by ``--network-cert-file`` as well as the network encryption public key used by members during recovery via ``--network-enc-pubk-file``.

The certificates and recovery public keys of initial members of the consortium are specified via ``--member-info``. For example, if 3 members should be added to CCF, operators should specify ``--member-info member1_cert.pem,member1_enc_pub.pem``, ``--member-info member2_cert.pem,member2_enc_pub.pem``, ``--member-info member3_cert.pem,member3_enc_pub.pem``.
.. note:: The network certificate should be distributed to users and members to be used as the certificate authority (CA) when establishing a TLS connection with any of the nodes part of the CCF network. When using curl, this is passed as the ``--cacert`` argument.

The certificates and recovery public keys of initial members of the consortium are specified via ``--member-info``. For example, if 3 members should be added to CCF, operators should specify ``--member-info member1_cert.pem,member1_enc_pubk.pem``, ``--member-info member2_cert.pem,member2_enc_pubk.pem``, ``--member-info member3_cert.pem,member3_enc_pubk.pem``.

The :term:`Constitution`, as defined by the initial members, should be passed via the ``--gov-script`` option.

Expand Down Expand Up @@ -112,7 +114,7 @@ Using a Configuration File

[<subcommand, one of [start, join, recover]>]
network-cert-file = <network-cert-file-name>
member-info = "<member_cert.pem>,<member_enc_pub.pem>"
member-info = "<member_cert.pem>,<member_enc_pubk.pem>"
gov-script = <gov-script-name>

.. code-block:: ini
Expand All @@ -127,7 +129,7 @@ Using a Configuration File

[<subcommand, one of [start, join, recover]>]
network-cert-file = <network-cert-file-name>
member-info = "<member_cert.pem>,<member_enc_pub.pem>"
member-info = "<member_cert.pem>,<member_enc_pubk.pem>"
gov-script = <gov-script-name>

To pass configuration files, use the ``--config`` option: ``./cchost --config=config.ini``. An error will be generated if the configuration file contains extra fields. Options in the configuration file will be read along with normal command line arguments. Additional information for configuration files in CLI11 can be found `here <https://cliutils.github.io/CLI11/book/chapters/config.html>`_.
Expand Down
15 changes: 1 addition & 14 deletions doc/schemas/recovery_share/submit_params.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"recovery_share": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "integer"
},
"type": "array"
}
},
"required": [
"recovery_share"
],
"title": "recovery_share/submit/params",
"type": "object"
"type": "string"
}
2 changes: 1 addition & 1 deletion doc/schemas/recovery_share/submit_result.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "recovery_share/submit/result",
"type": "boolean"
"type": "string"
}
20 changes: 5 additions & 15 deletions doc/schemas/recovery_share_result.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"encrypted_share": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "integer"
},
"type": "array"
"encrypted_recovery_share": {
"type": "string"
},
"nonce": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "integer"
},
"type": "array"
"type": "string"
}
},
"required": [
"nonce",
"encrypted_share"
"encrypted_recovery_share",
"nonce"
],
"title": "recovery_share/result",
"type": "object"
Expand Down
2 changes: 1 addition & 1 deletion doc/users/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ To generate the certificate and private key of trusted users should be generated

.. code-block:: bash

$ CCF/tests/keygenerator.sh --name=user1
$ CCF/tests/keygenerator.sh --name user1
-- Generating identity private key and certificate for participant "user1"...
Identity private key generated at: user1_privk.pem
Identity certificate generated at: user1_cert.pem (to be registered in CCF)
Expand Down
117 changes: 72 additions & 45 deletions src/node/rpc/member_frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ namespace ccf
DECLARE_JSON_REQUIRED_FIELDS(SetUserData, user_id)
DECLARE_JSON_OPTIONAL_FIELDS(SetUserData, user_data)

struct SubmitRecoveryShare
struct GetEncryptedRecoveryShare
{
std::vector<uint8_t> recovery_share;
std::string encrypted_recovery_share;
std::string nonce;

GetEncryptedRecoveryShare() = default;

GetEncryptedRecoveryShare(const EncryptedShare& encrypted_share_raw) :
encrypted_recovery_share(
tls::b64_from_raw(encrypted_share_raw.encrypted_share)),
nonce(tls::b64_from_raw(encrypted_share_raw.nonce))
{}
};
DECLARE_JSON_TYPE(SubmitRecoveryShare)
DECLARE_JSON_REQUIRED_FIELDS(SubmitRecoveryShare, recovery_share)
DECLARE_JSON_TYPE(GetEncryptedRecoveryShare)
DECLARE_JSON_REQUIRED_FIELDS(
GetEncryptedRecoveryShare, encrypted_recovery_share, nonce)

class MemberHandlers : public CommonHandlerRegistry
{
Expand Down Expand Up @@ -822,78 +832,91 @@ namespace ccf
Write)
.set_auto_schema<void, StateDigest>();

auto get_encrypted_recovery_share =
[this](RequestArgs& args, nlohmann::json&& params) {
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(
HTTP_STATUS_FORBIDDEN,
"Only active members are given recovery shares");
}
auto get_encrypted_recovery_share = [this](
RequestArgs& args,
nlohmann::json&& params) {
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(
HTTP_STATUS_FORBIDDEN,
"Only active members are given recovery shares");
}

auto encrypted_share =
share_manager.get_encrypted_share(args.tx, args.caller_id);
auto encrypted_share =
share_manager.get_encrypted_share(args.tx, args.caller_id);

if (!encrypted_share.has_value())
{
return make_error(
HTTP_STATUS_BAD_REQUEST,
fmt::format(
"Recovery share not found for member {}", args.caller_id));
}
if (!encrypted_share.has_value())
{
return make_error(
HTTP_STATUS_BAD_REQUEST,
fmt::format(
"Recovery share not found for member {}", args.caller_id));
}

return make_success(encrypted_share.value());
};
return make_success(GetEncryptedRecoveryShare(encrypted_share.value()));
};
install(
MemberProcs::GET_ENCRYPTED_RECOVERY_SHARE,
json_adapter(get_encrypted_recovery_share),
Read)
.set_auto_schema<void, EncryptedShare>()
.set_auto_schema<void, GetEncryptedRecoveryShare>()
.set_http_get_only();

auto submit_recovery_share = [this](
RequestArgs& args,
nlohmann::json&& params) {
auto submit_recovery_share = [this](ccf::RequestArgs& args) {
// Only active members can submit their shares for recovery
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(HTTP_STATUS_FORBIDDEN, "Member is not active");
args.rpc_ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
args.rpc_ctx->set_response_body("Member is not active");
return;
}

GenesisGenerator g(this->network, args.tx);
if (
g.get_service_status() != ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
{
return make_error(
HTTP_STATUS_FORBIDDEN,
args.rpc_ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
args.rpc_ctx->set_response_body(
"Service is not waiting for recovery shares");
return;
}

if (node.is_reading_private_ledger())
{
return make_error(
HTTP_STATUS_FORBIDDEN, "Node is already recovering private ledger");
args.rpc_ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
args.rpc_ctx->set_response_body(
"Node is already recovering private ledger");
return;
}

const auto in = params.get<SubmitRecoveryShare>();
const auto& in = args.rpc_ctx->get_request_body();
const auto s = std::string(in.begin(), in.end());
auto raw_recovery_share = tls::raw_from_b64(s);

size_t submitted_shares_count = 0;
try
{
submitted_shares_count = share_manager.submit_recovery_share(
args.tx, args.caller_id, in.recovery_share);
args.tx, args.caller_id, raw_recovery_share);
}
catch (const std::logic_error& e)
{
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
args.rpc_ctx->set_response_body(
fmt::format("Could not submit recovery share: {}", e.what()));
return;
}

if (submitted_shares_count < g.get_recovery_threshold())
{
// The number of shares required to re-assemble the secret has not yet
// been reached
return make_success(false);
args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
args.rpc_ctx->set_response_body(fmt::format(
"{}/{} recovery shares successfully submitted.",
submitted_shares_count,
g.get_recovery_threshold()));
return;
}

LOG_DEBUG_FMT(
Expand All @@ -907,19 +930,23 @@ namespace ccf
{
// For now, clear the submitted shares if combination fails.
share_manager.clear_submitted_recovery_shares(args.tx);
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
args.rpc_ctx->set_response_body(
fmt::format("Failed to initiate private recovery: {}", e.what()));
return;
}

share_manager.clear_submitted_recovery_shares(args.tx);
return make_success(true);

args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
args.rpc_ctx->set_response_body(fmt::format(
"{}/{} recovery shares successfully submitted. End of recovery "
"procedure initiated.",
submitted_shares_count,
g.get_recovery_threshold()));
};
install(
MemberProcs::SUBMIT_RECOVERY_SHARE,
json_adapter(submit_recovery_share),
Write)
.set_auto_schema<SubmitRecoveryShare, bool>();
install(MemberProcs::SUBMIT_RECOVERY_SHARE, submit_recovery_share, Write)
.set_auto_schema<std::string, std::string>();

auto create = [this](kv::Tx& tx, nlohmann::json&& params) {
LOG_DEBUG_FMT("Processing create RPC");
Expand Down
Loading