# # # delete "tests/database_is_closed_on_signal_exit" # # delete "tests/database_is_closed_on_signal_exit/__driver__.lua" # # patch "automate.cc" # from [58f559595f558158d76e5bb03c9884bb8a7681ef] # to [57c0c432f47e33c999104acdaa1bd38c65a1fae0] # # patch "cert.cc" # from [64cc2fa29bb75e7d79f4997c19207e7d566821e7] # to [468eea09a888a9d13318db7c7476010f1f6e1ae4] # # patch "cert.hh" # from [64e7525541766330034ae0436dfee7066e6b894a] # to [8bca058362ed2b204d8bf6cd799a7d77ebbad464] # # patch "cmd_db.cc" # from [c0385e17b2a21716167d0bbfd52905680397419f] # to [ccba678150255ac0223f11a3fb42dfaa33755237] # # patch "cmd_key_cert.cc" # from [7ff053c42f366c5cc0ee8fcc72e92ea7596f9b02] # to [a5afd0a653db10653e01d802e2e66d19d087ebed] # # patch "cmd_merging.cc" # from [ae9de0a973ad13985c41f2aa6ac22244a5e8653b] # to [c277f88ea57eb330c669a6f76c1d2b6ec1963897] # # patch "cmd_netsync.cc" # from [a246173f141d20b75f52ac5746a869557eff497e] # to [162ed994dc07aab810b1b05f1566e0ba1e4f5480] # # patch "cmd_othervcs.cc" # from [d3d91faf9411b7cd245353edd2469e0ed48a1614] # to [9d12334414fe5ac8ce461b4473b0fc11965a76cb] # # patch "cmd_ws_commit.cc" # from [871fe640df8e15eeb5aa6ee39469e349822d9d08] # to [e6f7f5cd2a2267683dd6d22944deda3dc308b7da] # # patch "key_store.cc" # from [d11a17a84b06232222a63b1558000809e67f4507] # to [37017048204ae6741ee9b3e48b6065ede644a09a] # # patch "key_store.hh" # from [9b2f66870fb8a087ec42bb4a8bcc5778bad625fb] # to [726c6a2b1295878948dfb7884969fa3b44d9f602] # # patch "keys.cc" # from [af4ec09e883c6e145c9cc5f931c0047e8d78000b] # to [1243cf1e9c5532e2944d6c679d2a5763e861bbad] # # patch "keys.hh" # from [565ab9f8faf9c3f3c9135107b9361033a587e674] # to [571830e357b1b545ac02b85ffdee5c4eadda3cf6] # # patch "netsync.cc" # from [3174e2b050480d6560e61699b342f0418cc6b29d] # to [03bb6c7bab712664c42d3b886492ef96cd0e3357] # # patch "project.cc" # from [1500110683fb64d9252d3e44f2e85a2694607352] # to [8f4a3f95b006e6c824bfdd32b9dfd75f3e85f824] # # patch "revision.cc" # from [725c74dec3553f8e4a5f5d341b7101518fb83aba] # to [515d7f7cf392614ea54f70bc9a58bf91495c6d7f] # ============================================================ --- automate.cc 58f559595f558158d76e5bb03c9884bb8a7681ef +++ automate.cc 57c0c432f47e33c999104acdaa1bd38c65a1fae0 @@ -2084,18 +2084,14 @@ CMD_AUTOMATE(cert, N_("REVISION-ID NAME F("wrong argument count")); CMD_REQUIRES_DATABASE(app); - - cert c; revision_id rid(idx(args, 0)()); - transaction_guard guard(db); N(db.revision_exists(rid), F("no such revision '%s'") % rid); - make_simple_cert(rid.inner(), cert_name(idx(args, 1)()), - cert_value(idx(args, 2)()), db, app.keys, c); - revision rc(c); - db.put_revision_cert(rc); - guard.commit(); + + cache_user_key(app.opts, app.lua, app.keys, app.db); + put_simple_revision_cert(rid, cert_name(idx(args, 1)()), + cert_value(idx(args, 2)()), db, app.keys); } // Name: get_db_variables ============================================================ --- cert.cc 64cc2fa29bb75e7d79f4997c19207e7d566821e7 +++ cert.cc 468eea09a888a9d13318db7c7476010f1f6e1ae4 @@ -361,78 +361,38 @@ cert_hash_code(cert const & t, hexenc encoded_val; + encode_base64(val, encoded_val); + cert t(id.inner(), nm, encoded_val, keys.signing_key); + string signed_text; cert_signable_text(t, signed_text); load_key_pair(keys, t.key); keys.make_signature(db, t.key, signed_text, t.sig); -} -cert_status -check_cert(database & db, cert const & t) -{ - string signed_text; - cert_signable_text(t, signed_text); - return db.check_signature(t.key, signed_text, t.sig); + revision cc(t); + return db.put_revision_cert(cc); } // "special certs" -void -get_user_key(rsa_keypair_id & key, key_store & keys, database & db) -{ - if (keys.has_opt_signing_key()) - key = keys.get_opt_signing_key(); - else if (keys.hook_get_current_branch_key(key)) - ; // the check also sets the key. - else - { - vector all_privkeys; - keys.get_key_ids(all_privkeys); - N(!all_privkeys.empty(), - F("you have no private key to make signatures with\n" - "perhaps you need to 'genkey '")); - N(all_privkeys.size() == 1, - F("you have multiple private keys\n" - "pick one to use for signatures by adding " - "'-k' to your command")); - key = all_privkeys[0]; - } - - if (db.database_specified() && db.public_key_exists(key)) - { - base64 pub_key; - keypair priv_key; - db.get_key(key, pub_key); - keys.get_key_pair(key, priv_key); - E(keys_match(key, pub_key, key, priv_key.pub), - F("The key '%s' stored in your database does\n" - "not match the version in your local key store!") % key); - } -} - // Guess which branch is appropriate for a commit below IDENT. // OPTS may override. Branch name is returned in BRANCHNAME. // Does not modify branch state in OPTS. @@ -476,36 +436,6 @@ void } void -make_simple_cert(hexenc const & id, - cert_name const & nm, - cert_value const & cv, - database & db, - key_store & keys, - cert & c) -{ - rsa_keypair_id key; - get_user_key(key, keys, db); - base64 encoded_val; - encode_base64(cv, encoded_val); - cert t(id, nm, encoded_val, key); - calculate_cert(keys, db, t); - c = t; -} - -void -put_simple_revision_cert(revision_id const & id, - cert_name const & nm, - cert_value const & val, - database & db, - key_store & keys) -{ - cert t; - make_simple_cert(id.inner(), nm, val, db, keys, t); - revision cc(t); - db.put_revision_cert(cc); -} - -void cert_revision_in_branch(revision_id const & rev, branch_name const & branch, database & db, ============================================================ --- cert.hh 64e7525541766330034ae0436dfee7066e6b894a +++ cert.hh 8bca058362ed2b204d8bf6cd799a7d77ebbad464 @@ -67,23 +67,7 @@ cert_status check_cert(database & db, ce void cert_signable_text(cert const & t,std::string & out); cert_status check_cert(database & db, cert const & t); -void load_key_pair(key_store & keys, - rsa_keypair_id const & id); - -void load_key_pair(key_store & keys, - rsa_keypair_id const & id, - keypair & kp); - -// Only used in cert.cc, and in revision.cc in what looks -// like migration code. -void make_simple_cert(hexenc const & id, - cert_name const & nm, - cert_value const & cv, - database & db, - key_store & keys, - cert & c); - -void put_simple_revision_cert(revision_id const & id, +bool put_simple_revision_cert(revision_id const & id, cert_name const & nm, cert_value const & val, database & db, @@ -109,11 +93,7 @@ cert_revision_in_branch(revision_id cons // conventions. you should use these unless you have a compelling // reason not to. -// N()'s out if there is no unique key for us to use void -get_user_key(rsa_keypair_id & key, key_store & keys, database & db); - -void guess_branch(revision_id const & id, options & opts, project_t & project, branch_name & branchname); void ============================================================ --- cmd_db.cc c0385e17b2a21716167d0bbfd52905680397419f +++ cmd_db.cc ccba678150255ac0223f11a3fb42dfaa33755237 @@ -226,9 +226,7 @@ CMD(db_changesetify, "changesetify", "", app.db.check_is_not_rosterified(); // early short-circuit to avoid failure after lots of work - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - require_password(key, app.keys, app.db); + cache_user_key(app.opts, app.lua, app.keys, app.db); build_changesets_from_manifest_ancestry(app.db, app.keys, set()); } @@ -245,9 +243,7 @@ CMD(db_rosterify, "rosterify", "", CMD_R app.db.check_is_not_rosterified(); // early short-circuit to avoid failure after lots of work - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - require_password(key, app.keys, app.db); + cache_user_key(app.opts, app.lua, app.keys, app.db); build_roster_style_revs_from_manifest_style_revs(app.db, app.keys, app.opts.attrs_to_drop); ============================================================ --- cmd_key_cert.cc 7ff053c42f366c5cc0ee8fcc72e92ea7596f9b02 +++ cmd_key_cert.cc a5afd0a653db10653e01d802e2e66d19d087ebed @@ -45,17 +45,17 @@ CMD(genkey, "genkey", "", CMD_REF(key_an internalize_rsa_keypair_id(idx(args, 0), ident); bool exists = app.keys.key_pair_exists(ident); if (app.db.database_specified()) - { - transaction_guard guard(app.db); - exists = exists || app.db.public_key_exists(ident); - guard.commit(); - } + exists = exists || app.db.public_key_exists(ident); N(!exists, F("key '%s' already exists") % ident); + utf8 phrase; + get_passphrase(phrase, ident, true, true); + keypair kp; P(F("generating key-pair '%s'") % ident); - generate_key_pair(app.keys, ident, kp); + generate_key_pair(kp, phrase); + P(F("storing key-pair '%s' in %s/") % ident % app.keys.get_key_dir()); app.keys.put_key_pair(ident, kp); @@ -114,11 +114,9 @@ CMD(passphrase, "passphrase", "", CMD_RE rsa_keypair_id ident; internalize_rsa_keypair_id(idx(args, 0), ident); - N(app.keys.key_pair_exists(ident), - F("key '%s' does not exist in the keystore") % ident); + keypair key; + load_key_pair(app.keys, ident, key); - keypair key; - app.keys.get_key_pair(ident, key); change_key_passphrase(app.keys, ident, key.priv); app.keys.delete_key(ident); app.keys.put_key_pair(ident, key); @@ -136,12 +134,11 @@ CMD(ssh_agent_export, "ssh_agent_export" rsa_keypair_id id; keypair key; - get_user_key(id, app.keys, app.db); - N(app.keys.key_pair_exists(id), F("the key you specified cannot be found")); + get_user_key(id, app.opts, app.lua, app.keys, app.db); app.keys.get_key_pair(id, key); shared_ptr priv = get_private_key(app.keys, id, key.priv); utf8 new_phrase; - get_passphrase(app.keys, id, new_phrase, true, true, "enter new passphrase"); + get_passphrase(new_phrase, id, true, false); Pipe p; p.start_msg(); if (new_phrase().length()) @@ -176,8 +173,7 @@ CMD(ssh_agent_add, "ssh_agent_add", "", rsa_keypair_id id; keypair key; - get_user_key(id, app.keys, app.db); - N(app.keys.key_pair_exists(id), F("the key you specified cannot be found")); + get_user_key(id, app.opts, app.lua, app.keys, app.db); app.keys.get_key_pair(id, key); shared_ptr priv = get_private_key(app.keys, id, key.priv); app.agent.add_identity(*priv, id()); @@ -200,8 +196,7 @@ CMD(cert, "cert", "", CMD_REF(key_and_ce cert_name cname; internalize_cert_name(idx(args, 1), cname); - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); + cache_user_key(app.opts, app.lua, app.keys, app.db); cert_value val; if (args.size() == 3) @@ -279,7 +274,9 @@ CMD(tag, "tag", "", CMD_REF(review), N_( revision_id r; complete(app, idx(args, 0)(), r); - cert_revision_tag(r, idx(args, 1)(), app.db, app.keys); + + cache_user_key(app.opts, app.lua, app.keys, app.db); + app.get_project().put_tag(app.keys, r, idx(args, 1)()); } @@ -294,6 +291,8 @@ CMD(testresult, "testresult", "", CMD_RE revision_id r; complete(app, idx(args, 0)(), r); + + cache_user_key(app.opts, app.lua, app.keys, app.db); cert_revision_testresult(r, idx(args, 1)(), app.db, app.keys); } @@ -310,6 +309,8 @@ CMD(approve, "approve", "", CMD_REF(revi complete(app, idx(args, 0)(), r); guess_branch(r, app.opts, app.get_project()); N(app.opts.branchname() != "", F("need --branch argument for approval")); + + cache_user_key(app.opts, app.lua, app.keys, app.db); app.get_project().put_revision_in_branch(app.keys, r, app.opts.branchname); } @@ -325,7 +326,10 @@ CMD(suspend, "suspend", "", CMD_REF(revi complete(app, idx(args, 0)(), r); guess_branch(r, app.opts, app.get_project()); N(app.opts.branchname() != "", F("need --branch argument to suspend")); - app.get_project().suspend_revision_in_branch(app.keys, r, app.opts.branchname); + + cache_user_key(app.opts, app.lua, app.keys, app.db); + app.get_project().suspend_revision_in_branch(app.keys, r, + app.opts.branchname); } CMD(comment, "comment", "", CMD_REF(review), N_("REVISION [COMMENT]"), @@ -352,6 +356,8 @@ CMD(comment, "comment", "", CMD_REF(revi revision_id r; complete(app, idx(args, 0)(), r); + + cache_user_key(app.opts, app.lua, app.keys, app.db); cert_revision_comment(r, comment, app.db, app.keys); } ============================================================ --- cmd_merging.cc ae9de0a973ad13985c41f2aa6ac22244a5e8653b +++ cmd_merging.cc c277f88ea57eb330c669a6f76c1d2b6ec1963897 @@ -350,12 +350,6 @@ merge_two(revision_id const & left, revi P(F("[right] %s") % right); } - { - // early short-circuit to avoid failure after lots of work - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - } - revision_id merged; transaction_guard guard(app.db); interactive_merge_and_store(left, right, merged, app.db, app.lua); @@ -404,6 +398,9 @@ CMD(merge, "merge", "", CMD_REF(tree), " P(FP("%d head on branch '%s'", "%d heads on branch '%s'", heads.size()) % heads.size() % app.opts.branchname); + // avoid failure after lots of work + cache_user_key(app.opts, app.lua, app.keys, app.db); + map heads_for_ancestor; set ancestors; size_t pass = 1, todo = heads.size() - 1; @@ -456,7 +453,8 @@ CMD(merge, "merge", "", CMD_REF(tree), " // corresponding pair of heads. revpair p = heads_for_ancestor[*ancestors.begin()]; - merge_two(p.first, p.second, app.opts.branchname, string("merge"), app, std::cout, false); + merge_two(p.first, p.second, app.opts.branchname, string("merge"), + app, std::cout, false); ancestors.clear(); heads_for_ancestor.clear(); @@ -475,7 +473,8 @@ CMD(merge, "merge", "", CMD_REF(tree), " revision_id right = *i++; I(i == heads.end()); - merge_two(left, right, app.opts.branchname, string("merge"), app, std::cout, false); + merge_two(left, right, app.opts.branchname, string("merge"), + app, std::cout, false); P(F("note: your workspaces have not been updated")); } @@ -544,18 +543,22 @@ CMD(merge_into_dir, "merge_into_dir", "" set::const_iterator src_i = src_heads.begin(); set::const_iterator dst_i = dst_heads.begin(); - P(F("propagating %s -> %s") % idx(args,0) % idx(args,1)); - P(F("[left] %s") % *src_i); - P(F("[right] %s") % *dst_i); - - // check for special cases if (*src_i == *dst_i || is_ancestor(*src_i, *dst_i, app.db)) { P(F("branch '%s' is up-to-date with respect to branch '%s'") % idx(args, 1)() % idx(args, 0)()); P(F("no action taken")); + return; } - else if (is_ancestor(*dst_i, *src_i, app.db)) + + cache_user_key(app.opts, app.lua, app.keys, app.db); + + P(F("propagating %s -> %s") % idx(args,0) % idx(args,1)); + P(F("[left] %s") % *src_i); + P(F("[right] %s") % *dst_i); + + // check for special cases + if (is_ancestor(*dst_i, *src_i, app.db)) { P(F("no merge necessary; putting %s in branch '%s'") % (*src_i) % idx(args, 1)()); @@ -618,10 +621,6 @@ CMD(merge_into_dir, "merge_into_dir", "" content_merge_database_adaptor dba(app.db, left_rid, right_rid, left_marking_map, right_marking_map); - { - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - } resolve_merge_conflicts(left_roster, right_roster, result, dba, app.lua); @@ -787,7 +786,11 @@ CMD(explicit_merge, "explicit_merge", "" N(!is_ancestor(right, left, app.db), F("%s is already an ancestor of %s") % right % left); - merge_two(left, right, branch, string("explicit merge"), app, std::cout, false); + // avoid failure after lots of work + cache_user_key(app.opts, app.lua, app.keys, app.db); + + merge_two(left, right, branch, string("explicit merge"), + app, std::cout, false); } CMD(show_conflicts, "show_conflicts", "", CMD_REF(informative), N_("REV REV"), ============================================================ --- cmd_netsync.cc a246173f141d20b75f52ac5746a869557eff497e +++ cmd_netsync.cc 162ed994dc07aab810b1b05f1566e0ba1e4f5480 @@ -75,14 +75,11 @@ find_key(utf8 const & addr, parse_uri(addr(), u); if (!u.host.empty()) host = utf8(u.host); - if (!app.lua.hook_get_netsync_key(host, include, exclude, key) - || key() == "") - { - if (needed) - { - get_user_key(key, app.keys, app.db); - } - } + if (needed + && (key().empty() + || !app.lua.hook_get_netsync_key(host, include, exclude, key))) + get_user_key(key, app.opts, app.lua, app.keys, app.db); + app.opts.signing_key = key; } @@ -93,13 +90,11 @@ find_key_if_needed(utf8 const & addr, app_state & app, bool needed = true) { - uri u; - parse_uri(addr(), u); + uri u; + parse_uri(addr(), u); - if (app.lua.hook_use_transport_auth(u)) - { - find_key(addr, include, exclude, app, needed); - } + if (app.lua.hook_use_transport_auth(u)) + find_key(addr, include, exclude, app, needed); } static void @@ -437,22 +432,23 @@ CMD_NO_WORKSPACE(serve, "serve", "", CMD pid_file pid(app.opts.pidfile); + app.db.ensure_open(); + if (app.opts.use_transport_auth) { + N(app.lua.hook_persist_phrase_ok(), + F("need permission to store persistent passphrase " + "(see hook persist_phrase_ok())")); + if (!app.opts.bind_uris.empty()) find_key(*app.opts.bind_uris.begin(), globish("*"), globish(""), app); else find_key(utf8(), globish("*"), globish(""), app); - - N(app.lua.hook_persist_phrase_ok(), - F("need permission to store persistent passphrase (see hook persist_phrase_ok())")); - require_password(app.opts.signing_key, app.keys, app.db); } else if (!app.opts.bind_stdio) - W(F("The --no-transport-auth option is usually only used in combination with --stdio")); + W(F("The --no-transport-auth option is usually only used " + "in combination with --stdio")); - app.db.ensure_open(); - run_netsync_protocol(server_voice, source_and_sink_role, app.opts.bind_uris, globish("*"), globish(""), app.get_project(), app.keys, app.lua, app.opts); ============================================================ --- cmd_othervcs.cc d3d91faf9411b7cd245353edd2469e0ed48a1614 +++ cmd_othervcs.cc 9d12334414fe5ac8ce461b4473b0fc11965a76cb @@ -51,9 +51,7 @@ CMD(cvs_import, "cvs_import", "", CMD_RE // make sure we can sign certs using the selected key; also requests // the password (if necessary) up front rather than after some arbitrary // amount of work - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - require_password(key, app.keys, app.db); + cache_user_key(app.opts, app.lua, app.keys, app.db); import_cvs_repo(cvsroot, app.keys, app.get_project(), app.opts.branchname); } ============================================================ --- cmd_ws_commit.cc 871fe640df8e15eeb5aa6ee39469e349822d9d08 +++ cmd_ws_commit.cc e6f7f5cd2a2267683dd6d22944deda3dc308b7da @@ -22,6 +22,7 @@ #include "ui.hh" #include "app_state.hh" #include "basic_io.hh" +#include "keys.hh" using std::cout; using std::make_pair; @@ -353,6 +354,8 @@ CMD(disapprove, "disapprove", "", CMD_RE process_commit_message_args(log_message_given, log_message, app, utf8((FL("disapproval of revision '%s'") % r).str())); + cache_user_key(app.opts, app.lua, app.keys, app.db); + edge_entry const & old_edge (*rev.edges.begin()); app.db.get_revision_manifest(edge_old_revision(old_edge), rev_inverse.new_manifest); @@ -1055,12 +1058,6 @@ CMD(commit, "commit", "ci", CMD_REF(work app.require_workspace(); - { - // fail early if there isn't a key - rsa_keypair_id key; - get_user_key(key, app.keys, app.db); - } - app.make_branch_sticky(); app.work.get_parent_rosters(old_rosters, app.db); app.work.get_current_roster_shape(new_roster, app.db, nis); @@ -1151,6 +1148,8 @@ CMD(commit, "commit", "ci", CMD_REF(work message_validated, reason); N(message_validated, F("log message rejected by hook: %s") % reason); + cache_user_key(app.opts, app.lua, app.keys, app.db); + // for the divergence check, below set heads; app.get_project().get_branch_heads(app.opts.branchname, heads, ============================================================ --- key_store.cc d11a17a84b06232222a63b1558000809e67f4507 +++ key_store.cc 37017048204ae6741ee9b3e48b6065ede644a09a @@ -429,6 +429,10 @@ key_store::make_signature(database & db, E(s == cert_ok, F("make_signature: signature is not valid")); } +// +// Hooks into the application configuration +// + bool key_store::hook_get_passphrase(rsa_keypair_id const & k, std::string & phrase) { @@ -441,24 +445,6 @@ key_store::hook_persist_phrase_ok() return s->app.lua.hook_persist_phrase_ok(); } -bool -key_store::hook_get_current_branch_key(rsa_keypair_id & k) -{ - return s->app.lua.hook_get_branch_key(s->app.opts.branchname, k); -} - -bool -key_store::has_opt_signing_key() -{ - return (s->app.opts.signing_key() != ""); -} - -rsa_keypair_id -key_store::get_opt_signing_key() -{ - return s->app.opts.signing_key; -} - // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- key_store.hh 9b2f66870fb8a087ec42bb4a8bcc5778bad625fb +++ key_store.hh 726c6a2b1295878948dfb7884969fa3b44d9f602 @@ -22,6 +22,8 @@ public: void maybe_read_key_dir(); public: + rsa_keypair_id signing_key; + key_store(app_state & a); ~key_store(); @@ -57,9 +59,6 @@ public: // the key_store context bool hook_get_passphrase(rsa_keypair_id const & k, std::string & phrase); bool hook_persist_phrase_ok(); - bool hook_get_current_branch_key(rsa_keypair_id & k); - bool has_opt_signing_key(); - rsa_keypair_id get_opt_signing_key(); }; // Local Variables: ============================================================ --- keys.cc af4ec09e883c6e145c9cc5f931c0047e8d78000b +++ keys.cc 1243cf1e9c5532e2944d6c679d2a5763e861bbad @@ -36,6 +36,7 @@ #include "charset.hh" #include "ssh_agent.hh" #include "database.hh" +#include "options.hh" using std::cout; using std::make_pair; @@ -72,24 +73,77 @@ do_arc4(SecureVector & sym_ payload = enc.read_all(); } -// 'force_from_user' means that we don't use the passphrase cache, and we -// don't use the get_passphrase hook. +// "raw" passphrase prompter; unaware of passphrase caching or the laziness +// hook. KEYID is used only in prompts. CONFIRM_PHRASE causes the user to +// be prompted to type the same thing twice, and will loop if they don't +// match. Prompts are worded slightly differently if GENERATING_KEY is true. void -get_passphrase(key_store & keys, +get_passphrase(utf8 & phrase, rsa_keypair_id const & keyid, - utf8 & phrase, bool confirm_phrase, - bool force_from_user, bool generating_key) { + string prompt1, prompt2; + char pass1[constants::maxpasswd]; + char pass2[constants::maxpasswd]; + int i = 0; + if (confirm_phrase && !generating_key) + prompt1 = (F("enter new passphrase for key ID [%s]: ") % keyid).str(); + else + prompt1 = (F("enter passphrase for key ID [%s]: ") % keyid).str(); + + if (confirm_phrase) + prompt2 = (F("confirm passphrase for key ID [%s]: ") % keyid).str(); + + try + { + for (;;) + { + memset(pass1, 0, constants::maxpasswd); + memset(pass2, 0, constants::maxpasswd); + ui.ensure_clean_line(); + + read_password(prompt1, pass1, constants::maxpasswd); + if (!confirm_phrase) + break; + + ui.ensure_clean_line(); + read_password(prompt2, pass2, constants::maxpasswd); + if (strcmp(pass1, pass2) == 0) + break; + + N(i++ < 2, F("too many failed passphrases")); + P(F("passphrases do not match, try again")); + } + + external ext_phrase(pass1); + system_to_utf8(ext_phrase, phrase); + } + catch (...) + { + memset(pass1, 0, constants::maxpasswd); + memset(pass2, 0, constants::maxpasswd); + throw; + } + memset(pass1, 0, constants::maxpasswd); + memset(pass2, 0, constants::maxpasswd); +} + +// 'force_from_user' means that we don't use the passphrase cache, and we +// don't use the get_passphrase hook. +static void +get_passphrase(key_store & keys, + utf8 & phrase, + rsa_keypair_id const & keyid, + bool force_from_user) +{ // we permit the user to relax security here, by caching a passphrase (if // they permit it) through the life of a program run. this helps when // you're making a half-dozen certs during a commit or merge or // something. bool persist_phrase = keys.hook_persist_phrase_ok(); static map phrases; - if (!force_from_user && phrases.find(keyid) != phrases.end()) { phrase = phrases[keyid]; @@ -99,78 +153,119 @@ get_passphrase(key_store & keys, string lua_phrase; if (!force_from_user && keys.hook_get_passphrase(keyid, lua_phrase)) { + N(!lua_phrase.empty(), + F("got empty passphrase from get_passphrase() hook")); + // user is being a slob and hooking lua to return his passphrase phrase = utf8(lua_phrase); - N(phrase != utf8(""), - F("got empty passphrase from get_passphrase() hook")); + return; } - else + + get_passphrase(phrase, keyid, false, false); + + // permit security relaxation. maybe. + if (persist_phrase) { - char pass1[constants::maxpasswd]; - char pass2[constants::maxpasswd]; - for (int i = 0; i < 3; ++i) - { - memset(pass1, 0, constants::maxpasswd); - memset(pass2, 0, constants::maxpasswd); - ui.ensure_clean_line(); - string prompt1 = ((confirm_phrase && !generating_key - ? F("enter new passphrase for key ID [%s]: ") - : F("enter passphrase for key ID [%s]: ")) - % keyid()).str(); + phrases.erase(keyid); + safe_insert(phrases, make_pair(keyid, phrase)); + } +} - read_password(prompt1, pass1, constants::maxpasswd); - if (confirm_phrase) - { - ui.ensure_clean_line(); - read_password((F("confirm passphrase for key ID [%s]: ") - % keyid()).str(), - pass2, constants::maxpasswd); - if (strcmp(pass1, pass2) == 0) - break; - else - { - P(F("passphrases do not match, try again")); - N(i < 2, F("too many failed passphrases")); - } - } - else - break; - } +// Loads a key pair for a given key id, considering it a user error +// if that key pair is not available. - try - { - external ext_phrase(pass1); - system_to_utf8(ext_phrase, phrase); +void +load_key_pair(key_store & keys, rsa_keypair_id const & id) +{ + N(keys.key_pair_exists(id), + F("no key pair '%s' found in key store '%s'") + % id % keys.get_key_dir()); +} - // permit security relaxation. maybe. - if (persist_phrase) - { - phrases.erase(keyid); - safe_insert(phrases, make_pair(keyid, phrase)); - } - } - catch (...) - { - memset(pass1, 0, constants::maxpasswd); - memset(pass2, 0, constants::maxpasswd); - throw; - } - memset(pass1, 0, constants::maxpasswd); - memset(pass2, 0, constants::maxpasswd); - } +void +load_key_pair(key_store & keys, + rsa_keypair_id const & id, + keypair & kp) +{ + load_key_pair(keys, id); + keys.get_key_pair(id, kp); } +// Find the key to be used for signing certs. If possible, ensure the +// database and the key_store agree on that key, and cache it in decrypted +// form, so as not to bother the user for their passphrase later. void -generate_key_pair(key_store & keys, // to hook for phrase - rsa_keypair_id const & id, // to prompting user for phrase - keypair & kp_out) +get_user_key(rsa_keypair_id & key, + options const & opts, lua_hooks & lua, + key_store & keys, database & db) { - utf8 phrase; - get_passphrase(keys, id, phrase, true, true, true); - generate_key_pair(kp_out, phrase); + if (!keys.signing_key().empty()) + { + key = keys.signing_key; + return; + } + + if (!opts.signing_key().empty()) + key = opts.signing_key; + else if (lua.hook_get_branch_key(opts.branchname, key)) + ; // the lua hook sets the key + else + { + vector all_privkeys; + keys.get_key_ids(all_privkeys); + N(all_privkeys.size() > 0, + F("you have no private key to make signatures with\n" + "perhaps you need to 'genkey '")); + N(all_privkeys.size() < 2, + F("you have multiple private keys\n" + "pick one to use for signatures by adding " + "'-k' to your command")); + + key = all_privkeys[0]; + } + + keys.signing_key = key; + + // Ensure that the specified key actually exists. + keypair priv_key; + load_key_pair(keys, key, priv_key); + + // we can only do the steps below if we have a database. + if (!db.database_specified()) + return; + + // If the database doesn't have this public key, add it now; otherwise + // make sure the database and key-store agree on the public key. + if (!db.public_key_exists(key)) + db.put_key(key, priv_key.pub); + else + { + base64 pub_key; + db.get_key(key, pub_key); + E(keys_match(key, pub_key, key, priv_key.pub), + F("The key '%s' stored in your database does\n" + "not match the version in your local key store!") % key); + } + + // If permitted, decrypt and cache the key now. + if (keys.hook_persist_phrase_ok()) + { + string plaintext("hi maude"); + base64 sig; + keys.make_signature(db, key, plaintext, sig); + } } +// As above, but does not report which key has been selected; for use when +// the important thing is to have selected one and cached the decrypted key. +void +cache_user_key(options const & opts, lua_hooks & lua, + key_store & keys, database & db) +{ + rsa_keypair_id key; + get_user_key(key, opts, lua, keys, db); +} void generate_key_pair(keypair & kp_out, @@ -239,7 +334,7 @@ get_private_key(key_store & keys, { for (int i = 0; i < 3; ++i) { - get_passphrase(keys, id, phrase, false, force); + get_passphrase(keys, phrase, id, force); L(FL("have %d-byte encrypted private key") % decoded_key().size()); try @@ -294,7 +389,7 @@ migrate_private_key(key_store & keys, { decrypted_key.set(reinterpret_cast(decoded_key().data()), decoded_key().size()); - get_passphrase(keys, id, phrase, false, force); + get_passphrase(keys, phrase, id, force); SecureVector sym_key; sym_key.set(reinterpret_cast(phrase().data()), phrase().size()); do_arc4(sym_key, decrypted_key); @@ -346,10 +441,11 @@ change_key_passphrase(key_store & keys, rsa_keypair_id const & id, base64< rsa_priv_key > & encoded_key) { - shared_ptr priv = get_private_key(keys, id, encoded_key, true); + shared_ptr priv + = get_private_key(keys, id, encoded_key, true); utf8 new_phrase; - get_passphrase(keys, id, new_phrase, true, true, "enter new passphrase"); + get_passphrase(new_phrase, id, true, false); Pipe p; p.start_msg(); @@ -403,31 +499,6 @@ void } void -read_pubkey(string const & in, - rsa_keypair_id & id, - base64 & pub) -{ - string tmp_id, tmp_key; - size_t pos = 0; - extract_variable_length_string(in, tmp_id, pos, "pubkey id"); - extract_variable_length_string(in, tmp_key, pos, "pubkey value"); - id = rsa_keypair_id(tmp_id); - encode_base64(rsa_pub_key(tmp_key), pub); -} - -void -write_pubkey(rsa_keypair_id const & id, - base64 const & pub, - string & out) -{ - rsa_pub_key pub_tmp; - decode_base64(pub, pub_tmp); - insert_variable_length_string(id(), out); - insert_variable_length_string(pub_tmp(), out); -} - - -void key_hash_code(rsa_keypair_id const & ident, base64 const & pub, hexenc & out) @@ -471,23 +542,6 @@ keys_match(rsa_keypair_id const & id1, return hash1 == hash2; } -void -require_password(rsa_keypair_id const & key, - key_store & keys, - database & db) -{ - N(keys.key_pair_exists(key), - F("no key pair '%s' found in key store '%s'") - % key % keys.get_key_dir()); - - if (keys.hook_persist_phrase_ok()) - { - string plaintext("hi maude"); - base64 sig; - keys.make_signature(db, key, plaintext, sig); - } -} - #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" ============================================================ --- keys.hh 565ab9f8faf9c3f3c9135107b9361033a587e674 +++ keys.hh 571830e357b1b545ac02b85ffdee5c4eadda3cf6 @@ -13,6 +13,8 @@ #include "vocab.hh" #include +struct options; +class lua_hooks; class key_store; class database; namespace Botan { class RSA_PrivateKey; } @@ -22,6 +24,26 @@ namespace Botan { class RSA_PrivateKey; // could in theory be in transforms.cc too, but that file's already kinda // big and this stuff "feels" different, imho. +void +get_passphrase(utf8 & phrase, + rsa_keypair_id const & keyid, + bool confirm_phrase, + bool generating_key); + +// N()'s out if there is no unique key for us to use +void get_user_key(rsa_keypair_id & key, options const & opts, lua_hooks & lua, + key_store & keys, database & db); + +void cache_user_key(options const & opts, lua_hooks & lua, + key_store & keys, database & db); + +void load_key_pair(key_store & keys, + rsa_keypair_id const & id); + +void load_key_pair(key_store & keys, + rsa_keypair_id const & id, + keypair & kp); + void generate_key_pair(key_store & keys, // to hook for phrase rsa_keypair_id const & id, // to prompting user for phrase keypair & kp_out); @@ -44,9 +66,6 @@ bool check_signature(key_store & keys, std::string const & alleged_text, base64 const & signature); -void require_password(rsa_keypair_id const & id, - key_store & keys, database & db); - void encrypt_rsa(key_store & keys, rsa_keypair_id const & id, base64 & pub, @@ -59,14 +78,6 @@ void decrypt_rsa(key_store & keys, rsa_oaep_sha_data const & ciphertext, std::string & plaintext); -void -get_passphrase(key_store & keys, - rsa_keypair_id const & keyid, - utf8 & phrase, - bool confirm_phrase = false, - bool force_from_user = false, - bool generating_key = false); - boost::shared_ptr get_private_key(key_store & keys, rsa_keypair_id const & id, @@ -75,14 +86,6 @@ get_private_key(key_store & keys, // netsync stuff -void read_pubkey(std::string const & in, - rsa_keypair_id & id, - base64 & pub); - -void write_pubkey(rsa_keypair_id const & id, - base64 const & pub, - std::string & out); - void key_hash_code(rsa_keypair_id const & ident, base64 const & pub, hexenc & out); ============================================================ --- netsync.cc 3174e2b050480d6560e61699b342f0418cc6b29d +++ netsync.cc 03bb6c7bab712664c42d3b886492ef96cd0e3357 @@ -281,6 +281,30 @@ require(bool check, string const & conte throw bad_decode(F("check of '%s' failed") % context); } +static void +read_pubkey(string const & in, + rsa_keypair_id & id, + base64 & pub) +{ + string tmp_id, tmp_key; + size_t pos = 0; + extract_variable_length_string(in, tmp_id, pos, "pubkey id"); + extract_variable_length_string(in, tmp_key, pos, "pubkey value"); + id = rsa_keypair_id(tmp_id); + encode_base64(rsa_pub_key(tmp_key), pub); +} + +static void +write_pubkey(rsa_keypair_id const & id, + base64 const & pub, + string & out) +{ + rsa_pub_key pub_tmp; + decode_base64(pub, pub_tmp); + insert_variable_length_string(id(), out); + insert_variable_length_string(pub_tmp(), out); +} + struct netsync_error { string msg; ============================================================ --- project.cc 1500110683fb64d9252d3e44f2e85a2694607352 +++ project.cc 8f4a3f95b006e6c824bfdd32b9dfd75f3e85f824 @@ -10,6 +10,7 @@ #include "revision.hh" #include "transforms.hh" #include "lua_hooks.hh" +#include "keys.hh" using std::string; using std::set; @@ -361,7 +362,7 @@ project_t::put_standard_certs_from_optio if (author.empty()) { rsa_keypair_id key; - get_user_key(key, keys, db); + get_user_key(key, opts, lua, keys, db); if (!lua.hook_get_author(branch, key, author)) author = key(); ============================================================ --- revision.cc 725c74dec3553f8e4a5f5d341b7101518fb83aba +++ revision.cc 515d7f7cf392614ea54f70bc9a58bf91495c6d7f @@ -942,10 +942,7 @@ void anc_graph::write_certs() cert_name name(j->second.first); cert_value val(j->second.second); - cert new_cert; - make_simple_cert(rev.inner(), name, val, db, keys, new_cert); - revision rcert(new_cert); - if (db.put_revision_cert(rcert)) + if (put_simple_revision_cert(rev, name, val, db, keys)) ++n_certs_out; } }