diff --git a/rhizome.h b/rhizome.h index d790b387..822971da 100644 --- a/rhizome.h +++ b/rhizome.h @@ -344,6 +344,8 @@ int rhizome_store_cleanup(struct rhizome_cleanup_report *report); void rhizome_vacuum_db(sqlite_retry_state *retry); int rhizome_manifest_createid(rhizome_manifest *m); int rhizome_get_bundle_from_seed(rhizome_manifest *m, const char *seed); +int rhizome_get_bundle_from_secret(rhizome_manifest *m, const rhizome_bk_t *bsk); +int rhizome_new_bundle_from_secret(rhizome_manifest *m, const rhizome_bk_t *bsk); struct rhizome_manifest_summary { rhizome_bid_t bid; diff --git a/rhizome_cli.c b/rhizome_cli.c index 3781152a..7a07a626 100644 --- a/rhizome_cli.c +++ b/rhizome_cli.c @@ -112,7 +112,7 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont { if (config.debug.verbose) DEBUG_cli_parsed(parsed); - const char *filepath, *manifestpath, *manifestid, *authorSidHex, *bskhex; + const char *filepath, *manifestpath, *manifestid, *authorSidHex, *bsktext; int force_new = 0 == cli_arg(parsed, "--force-new", NULL, NULL, NULL); cli_arg(parsed, "filepath", &filepath, NULL, ""); @@ -120,20 +120,19 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont return -1; cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); cli_arg(parsed, "manifestid", &manifestid, NULL, ""); - if (cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_secret_key, NULL) == -1) + if (cli_arg(parsed, "bsk", &bsktext, cli_optional_bundle_secret_key, NULL) == -1) return -1; sid_t authorSid; if (authorSidHex[0] && str_to_sid_t(&authorSid, authorSidHex) == -1) return WHYF("invalid author_sid: %s", authorSidHex); - rhizome_bk_t bsk; // treat empty string the same as null - if (bskhex && !*bskhex) - bskhex=NULL; - - if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1) - return WHYF("invalid bsk: \"%s\"", bskhex); + if (bsktext && !*bsktext) + bsktext = NULL; + rhizome_bk_t bsk; + if (bsktext && str_to_rhizome_bsk_t(&bsk, bsktext) == -1) + return WHYF("invalid bsk: \"%s\"", bsktext); int journal = strcasecmp(parsed->args[1], "journal")==0; @@ -143,19 +142,16 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont if (!(keyring = keyring_open_instance_cli(parsed))) return -1; - if (rhizome_opendb() == -1){ - keyring_free(keyring); - keyring = NULL; - return -1; - } + int ret = -1; + rhizome_manifest *m = NULL; + if (rhizome_opendb() == -1) + goto finish; /* Create a new manifest that will represent the file. If a manifest file was supplied, then read * it, otherwise create a blank manifest. */ - rhizome_manifest *m = rhizome_new_manifest(); - if (!m){ - keyring_free(keyring); - keyring = NULL; - return WHY("Manifest struct could not be allocated -- not added to rhizome"); + if ((m = rhizome_new_manifest()) == NULL){ + ret = WHY("Manifest struct could not be allocated -- not added to rhizome"); + goto finish; } if (manifestpath && *manifestpath && access(manifestpath, R_OK) == 0) { if (config.debug.rhizome) @@ -165,26 +161,20 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont trying to write it out. However, we do insist that whatever we load is parsed okay and not malformed. */ if (rhizome_read_manifest_from_file(m, manifestpath) || m->malformed) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Manifest file could not be loaded -- not added to rhizome"); + ret = WHY("Manifest file could not be loaded -- not added to rhizome"); + goto finish; } } else if (manifestid && *manifestid) { if (config.debug.rhizome) DEBUGF("Reading manifest from database"); rhizome_bid_t bid; if (str_to_rhizome_bid_t(&bid, manifestid) == -1) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestid)); + ret = WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestid)); + goto finish; } - if (rhizome_retrieve_manifest(&bid, m) != RHIZOME_BUNDLE_STATUS_SAME){ - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest could not be loaded -- not added to rhizome"); + if (rhizome_retrieve_manifest(&bid, m) != RHIZOME_BUNDLE_STATUS_SAME) { + ret = WHY("Existing manifest could not be loaded -- not added to rhizome"); + goto finish; } } else { if (config.debug.rhizome) @@ -196,64 +186,80 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont } if (journal && !m->is_journal){ - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest is not a journal"); + ret = WHY("Existing manifest is not a journal"); + goto finish; } if (!journal && m->is_journal) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest is a journal"); + ret = WHY("Existing manifest is a journal"); + goto finish; } - if (bskhex) - rhizome_apply_bundle_secret(m, &bsk); + if (bsktext) { + if (m->has_id) { + if (!rhizome_apply_bundle_secret(m, &bsk)) { + ret = WHY("Supplied bundle secret does not match Bundle Id"); + goto finish; + } + } else { + if (rhizome_new_bundle_from_secret(m, &bsk) == -1) { + ret = WHY("Failed to create bundle from given secret"); + goto finish; + } + } + } if (m->service == NULL) rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE); - if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return -1; - } + if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) + goto finish; - enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_NEW; enum rhizome_payload_status pstatus; if (journal){ pstatus = rhizome_append_journal_file(m, 0, filepath); + if (config.debug.rhizome) + DEBUGF("rhizome_append_journal_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); } else { pstatus = rhizome_stat_payload_file(m, filepath); + if (config.debug.rhizome) + DEBUGF("rhizome_stat_payload_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); assert(m->filesize != RHIZOME_SIZE_UNSET); if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) { assert(m->filesize > 0); pstatus = rhizome_store_payload_file(m, filepath); + if (config.debug.rhizome) + DEBUGF("rhizome_store_payload_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); } } + enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_ERROR; + int pstatus_valid = 0; switch (pstatus) { case RHIZOME_PAYLOAD_STATUS_EMPTY: case RHIZOME_PAYLOAD_STATUS_STORED: case RHIZOME_PAYLOAD_STATUS_NEW: + pstatus_valid = 1; + status = RHIZOME_BUNDLE_STATUS_NEW; break; case RHIZOME_PAYLOAD_STATUS_TOO_BIG: case RHIZOME_PAYLOAD_STATUS_EVICTED: + pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_NO_ROOM; INFO("Insufficient space to store payload"); break; case RHIZOME_PAYLOAD_STATUS_ERROR: + pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_ERROR; break; case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: + pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_INCONSISTENT; break; case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: + pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_READONLY; break; - default: - FATALF("pstatus = %d", pstatus); } + if (!pstatus_valid) + FATALF("pstatus = %d", pstatus); rhizome_manifest *mout = NULL; if (status == RHIZOME_BUNDLE_STATUS_NEW) { if (!rhizome_manifest_validate(m) || m->malformed) @@ -301,10 +307,12 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont FATALF("status=%d", status); if (mout && mout != m) rhizome_manifest_free(mout); + ret = status; +finish: rhizome_manifest_free(m); keyring_free(keyring); keyring = NULL; - return status; + return ret; } DEFINE_CMD(app_rhizome_import_bundle, 0, @@ -486,11 +494,11 @@ static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_conte { if (config.debug.verbose) DEBUG_cli_parsed(parsed); - const char *manifestpath, *filepath, *manifestid, *bskhex; + const char *manifestpath, *filepath, *manifestid, *bsktext; if ( cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, "") == -1 || cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 - || cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_secret_key, NULL) == -1) + || cli_arg(parsed, "bsk", &bsktext, cli_optional_bundle_secret_key, NULL) == -1) return -1; int extract = strcasecmp(parsed->args[1], "extract")==0; @@ -504,31 +512,28 @@ static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_conte if (!(keyring = keyring_open_instance_cli(parsed))) return -1; + rhizome_manifest *m = NULL; int ret=0; rhizome_bid_t bid; - if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHY("Invalid manifest ID"); + if (str_to_rhizome_bid_t(&bid, manifestid) == -1) { + ret = WHY("Invalid manifest ID"); + goto finish; } // treat empty string the same as null - if (bskhex && !*bskhex) - bskhex=NULL; + if (bsktext && !*bsktext) + bsktext = NULL; rhizome_bk_t bsk; - if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHYF("invalid bsk: \"%s\"", bskhex); + if (bsktext && str_to_rhizome_bsk_t(&bsk, bsktext) == -1) { + ret = WHYF("invalid bsk: \"%s\"", bsktext); + goto finish; } - rhizome_manifest *m = rhizome_new_manifest(); - if (m==NULL){ - keyring_free(keyring); - keyring = NULL; - return WHY("Out of manifests"); + if ((m = rhizome_new_manifest()) == NULL) { + ret = WHY("Out of manifests"); + goto finish; } switch(rhizome_retrieve_manifest(&bid, m)){ @@ -539,7 +544,7 @@ static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_conte if (ret==0){ assert(m->finalised); - if (bskhex) + if (bsktext) rhizome_apply_bundle_secret(m, &bsk); rhizome_authenticate_author(m); assert(m->authorship != AUTHOR_LOCAL); @@ -593,8 +598,8 @@ static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_conte default: FATALF("pstatus = %d", pstatus); } - if (m) - rhizome_manifest_free(m); +finish: + rhizome_manifest_free(m); keyring_free(keyring); keyring = NULL; return ret; diff --git a/rhizome_crypto.c b/rhizome_crypto.c index 7105bd5d..be3fd46c 100644 --- a/rhizome_crypto.c +++ b/rhizome_crypto.c @@ -42,19 +42,15 @@ int rhizome_manifest_createid(rhizome_manifest *m) return 0; } -struct signing_key{ +struct signing_key { unsigned char Private[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]; rhizome_bid_t Public; }; -/* generate a keypair from a given seed string */ -static int generate_keypair(const char *seed, struct signing_key *key) +/* generate a keypair from a given secret key */ +static int generate_keypair_from_secret(const rhizome_bk_t *bsk, struct signing_key *key) { - unsigned char hash[crypto_hash_sha512_BYTES]; - crypto_hash_sha512(hash, (unsigned char *)seed, strlen(seed)); - - // The first 256 bits (32 bytes) of the hash will be used as the private key of the BID. - bcopy(hash, key->Private, sizeof key->Private); + bcopy(bsk->binary, key->Private, sizeof bsk->binary); // first 32 bytes if (crypto_sign_compute_public_key(key->Private, key->Public.binary) == -1) return WHY("Could not generate public key"); // The last 32 bytes of the private key should be identical to the public key. This is what @@ -68,15 +64,24 @@ static int generate_keypair(const char *seed, struct signing_key *key) * Then either fetch it from the database or initialise a new empty manifest */ int rhizome_get_bundle_from_seed(rhizome_manifest *m, const char *seed) { - struct signing_key key; - if (generate_keypair(seed, &key)) + union { + unsigned char hash[crypto_hash_sha512_BYTES]; + rhizome_bk_t bsk; + } u; + crypto_hash_sha512(u.hash, (unsigned char *)seed, strlen(seed)); + // The first 256 bits (32 bytes) of the hash will be used as the private key of the BID. + return rhizome_get_bundle_from_secret(m, &u.bsk); +} + +/* Generate a bundle id deterministically from the given bundle secret key. + * Then either fetch it from the database or initialise a new empty manifest + */ +int rhizome_get_bundle_from_secret(rhizome_manifest *m, const rhizome_bk_t *bsk) +{ + if (rhizome_new_bundle_from_secret(m, bsk) == -1) return -1; - - switch(rhizome_retrieve_manifest(&key.Public, m)){ + switch (rhizome_retrieve_manifest(&m->cryptoSignPublic, m)) { case RHIZOME_BUNDLE_STATUS_NEW: - // manifest not retrieved - rhizome_manifest_set_id(m, &key.Public); // zerofills m->cryptoSignSecret - m->haveSecret = NEW_BUNDLE_ID; break; case RHIZOME_BUNDLE_STATUS_SAME: m->haveSecret = EXISTING_BUNDLE_ID; @@ -84,7 +89,19 @@ int rhizome_get_bundle_from_seed(rhizome_manifest *m, const char *seed) default: return -1; } - + return 0; +} + +/* Generate a bundle id deterministically from the given bundle secret key. + * Then initialise a new empty manifest. + */ +int rhizome_new_bundle_from_secret(rhizome_manifest *m, const rhizome_bk_t *bsk) +{ + struct signing_key key; + if (generate_keypair_from_secret(bsk, &key)) + return -1; + rhizome_manifest_set_id(m, &key.Public); // zerofills m->cryptoSignSecret + m->haveSecret = NEW_BUNDLE_ID; bcopy(key.Private, m->cryptoSignSecret, sizeof m->cryptoSignSecret); // Disabled for performance, these asserts should nevertheless always hold. //assert(cmp_rhizome_bid_t(&m->cryptoSignPublic, &key.Public) == 0); diff --git a/testdefs_rhizome.sh b/testdefs_rhizome.sh index a1816a94..84c37943 100644 --- a/testdefs_rhizome.sh +++ b/testdefs_rhizome.sh @@ -35,7 +35,6 @@ assert_manifest_complete() { tfw_cat -v "$manifest" assertGrep "$manifest" "^service=$rexp_service\$" assertGrep "$manifest" "^id=$rexp_manifestid\$" - assertGrep "$manifest" "^BK=$rexp_bundlekey\$" assertGrep "$manifest" "^date=$rexp_date\$" assertGrep "$manifest" "^version=$rexp_version\$" assertGrep "$manifest" "^filesize=$rexp_filesize\$" diff --git a/tests/rhizomeops b/tests/rhizomeops index 662785fa..d69f55bd 100755 --- a/tests/rhizomeops +++ b/tests/rhizomeops @@ -782,6 +782,33 @@ test_AddServiceUnsupported() { tfw_cat --stdout --stderr } +doc_AddUpdateNoAuthorWithPassphrase="Add and update authorless bundle with only secret passphrase" +setup_AddUpdateNoAuthorWithPassphrase() { + setup_servald + setup_rhizome + create_file file1 1k + create_file file1_2 2k + pass='On the Ning Nang Nong' +} +test_AddUpdateNoAuthorWithPassphrase() { + executeOk_servald rhizome add file '' file1 file1.manifest "#$pass" + tfw_cat --stdout --stderr + assert_stdout_add_file file1 !.author '!BK' + assert_manifest_complete file1.manifest + extract_manifest_id BID file1.manifest + executeOk_servald rhizome list + assert_rhizome_list --fromhere=0 file1 + executeOk_servald rhizome add file '' file1_2 file1_2.manifest "#$pass" + tfw_cat --stdout --stderr + assert_stdout_add_file file1_2 !.author '!BK' + assert_manifest_complete file1_2.manifest + extract_manifest_id BID2 file1.manifest + assert [ "$BID" = "$BID2" ] + executeOk_servald rhizome extract file $BID file1x + tfw_cat --stdout --stderr + assert diff file1_2 file1x +} + doc_EncryptedPayload="Add and extract an encrypted payload" setup_EncryptedPayload() { setup_servald