# # patch "ChangeLog" # from [e515f351203cbbb76453853ed4316f300572c411] # to [a0176d1ce2159ad9ca88c5e135cc89126b660e96] # # patch "cert.cc" # from [fdf5188e96328a6d86fcac31ff3caf0b8ebb7602] # to [c475f03b34ae549434d09247abd7227b7c0a90f7] # # patch "commands.cc" # from [30f92c2f7eee55dda3d43ae5643224edba7c59c1] # to [3a5202dce2bd8a380ac9bf9b2ad736eca43dd25e] # # patch "database.cc" # from [7a37326e3fb76c441dde9a7000a5cbf187696ea8] # to [a3c4507bf09e5aade2dd702ddc8c91f51d51df12] # # patch "database.hh" # from [be5bc6fef85bbfd317b0d94d1b6329167a015a28] # to [4935d9f82c7eb489d96f79285db844a611c5a095] # # patch "key_store.cc" # from [05ee19ff4146a3ef4b9b1751e5dbf39a55f26a50] # to [12e5e98f0e37a8c9a48ac545358b9d8c316099e9] # # patch "keys.hh" # from [37f54b83f95952ba438899513ca0991cc63d4d10] # to [969e5b32cfc2d56bfe239077a3104767d9ad72d7] # # patch "schema.sql" # from [513edf45fe7238d23513b7062d965000271e2d67] # to [d25e530b103ebfafad30760aeac1dc01393e15f1] # # patch "schema_migration.cc" # from [572aa9fa72d4f32c72120d2792b8e27961ffa610] # to [6c6c942ba5f4bfafe82145ea516c57dc4a3c8b39] # # patch "tests/t_migrate_schema.at" # from [2074b20ad00853b9490ce370b6e6002bf2fe52f2] # to [be3ab9479b942b36b7b997abfa21f47e7457d78b] # ======================================================================== --- ChangeLog e515f351203cbbb76453853ed4316f300572c411 +++ ChangeLog a0176d1ce2159ad9ca88c5e135cc89126b660e96 @@ -1,5 +1,18 @@ 2005-09-27 Timothy Brownawell + Replace extract_keys command with a database migrator. + * database.{cc,hh}: Remove last traces of private keys in db. + * schema.sql: Remove private_keys table + * commands.cc (dropkey): private keys can no longer be in the db. + * schema_migration.cc: new migrator + * commands.cc: remove extract_keys + * tests/t_migrate_schema.at: update + * keys.hh, cert.cc, key_store.cc: keys_match doesn't work well on + privkeys. Don't use it. + * key_store.cc: Allow duplicate insertions, if they match. + +2005-09-27 Timothy Brownawell + * schema_migration.hh: migrate_depot_schema() doesn't really exist * database.cc, schema_migration.{cc,hh}: migrate_monotone_schema now takes an app_state * , so data can be moved to/from the database. ======================================================================== --- cert.cc fdf5188e96328a6d86fcac31ff3caf0b8ebb7602 +++ cert.cc c475f03b34ae549434d09247abd7227b7c0a90f7 @@ -368,8 +368,8 @@ { // We really don't want the database key and the rcfile key // to differ. - N(keys_match(id, kskeys.priv, id, luakeys.priv) - && keys_match(id, kskeys.pub, id, luakeys.pub), + N(/*keys_match(id, kskeys.priv, id, luakeys.priv) + && */keys_match(id, kskeys.pub, id, luakeys.pub), F("mismatch between key '%s' in key store" " and get_key_pair hook") % id); } ======================================================================== --- commands.cc 30f92c2f7eee55dda3d43ae5643224edba7c59c1 +++ commands.cc 3a5202dce2bd8a380ac9bf9b2ad736eca43dd25e @@ -873,16 +873,6 @@ key_deleted = true; } - if (app.db.private_key_exists(ident)) - { - P(F("dropping private key '%s' from database\n\n") % ident); - W(F("the private key data may not have been erased from the\n" - "database. it is recommended that you use 'db dump' and\n" - "'db load' to be sure.")); - app.db.delete_private_key(ident); - key_deleted = true; - } - if (app.keys.key_pair_exists(ident)) { P(F("dropping key pair '%s' from key store\n\n") % ident); @@ -3782,30 +3772,4 @@ app.db.clear_var(k); } -CMD(extract_keys, N_("debug"), N_(""), - N_("copy all private keys in the database into the keystore"), - OPT_NONE) -{ - std::vector privkeys; - app.db.get_private_keys(privkeys); - for (std::vector::const_iterator i = privkeys.begin(); - i != privkeys.end(); ++i) - { - base64< arc4 > old_priv; - app.db.get_key(*i, old_priv); - // convert it to a newstyle key - keypair kp; - migrate_private_key(app, *i, old_priv, kp); - - // check the public key matches - base64< rsa_pub_key > pub; - app.db.get_key(*i, pub); - MM(pub); - MM(kp.pub); - N(keys_match(*i, pub, *i, kp.pub), F("public and private keys for %s don't match") % (*i)()); - - app.keys.put_key_pair(*i, kp); - } -} - }; // namespace commands ======================================================================== --- database.cc 7a37326e3fb76c441dde9a7000a5cbf187696ea8 +++ database.cc a3c4507bf09e5aade2dd702ddc8c91f51d51df12 @@ -66,7 +66,7 @@ // non-alphabetic ordering of tables in sql source files. we could create // a temporary db, write our intended schema into it, and read it back, // but this seems like it would be too rude. possibly revisit this issue. - schema("1509fd75019aebef5ac3da3a5edf1312393b70e9"), + schema("bd86f9a90b5d552f0be1fa9aee847ea0f317778b"), __sql(NULL), transaction_level(0) {} @@ -514,22 +514,6 @@ ++pubkeys; } } - - { - // rehash all privkeys - results res; - fetch(res, 2, any_rows, "SELECT id, keydata FROM private_keys"); - execute("DELETE FROM private_keys"); - for (size_t i = 0; i < res.size(); ++i) - { - hexenc tmp; - key_hash_code(rsa_keypair_id(res[i][0]), base64< rsa_priv_key >(res[i][1]), tmp); - execute("INSERT INTO private_keys VALUES(?, ?, ?)", - tmp().c_str(), res[i][0].c_str(), res[i][1].c_str()); - ++privkeys; - } - } - guard.commit(); } @@ -1480,12 +1464,6 @@ get_keys("public_keys", keys); } -void -database::get_private_keys(vector & keys) -{ - get_keys("private_keys", keys); -} - bool database::public_key_exists(hexenc const & hash) { @@ -1512,20 +1490,6 @@ return false; } -bool -database::private_key_exists(rsa_keypair_id const & id) -{ - results res; - fetch(res, one_col, any_rows, - "SELECT id FROM private_keys WHERE id = ?", - id().c_str()); - I((res.size() == 1) || (res.size() == 0)); - if (res.size() == 1) - return true; - return false; -} - - void database::get_pubkey(hexenc const & hash, rsa_keypair_id & id, @@ -1564,24 +1528,6 @@ } void -database::get_key(rsa_keypair_id const & priv_id, - base64< arc4 > & priv_encoded) -{ - results res; - fetch(res, one_col, one_col, - "SELECT keydata FROM private_keys WHERE id = ?", - priv_id().c_str()); - priv_encoded = res[0][0]; -} - -void -database::delete_private_key(rsa_keypair_id const & pub_id) -{ - execute("DELETE FROM private_keys WHERE id = ?", - pub_id().c_str()); -} - -void database::delete_public_key(rsa_keypair_id const & pub_id) { execute("DELETE FROM public_keys WHERE id = ?", @@ -2060,16 +2006,7 @@ pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) - completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); - - res.clear(); - - fetch(res, 2, any_rows, - "SELECT hash, id FROM private_keys WHERE hash GLOB ?", - pattern.c_str()); - - for (size_t i = 0; i < res.size(); ++i) - completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); + completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); } using selectors::selector_type; ======================================================================== --- database.hh be5bc6fef85bbfd317b0d94d1b6329167a015a28 +++ database.hh 4935d9f82c7eb489d96f79285db844a611c5a095 @@ -320,13 +320,10 @@ void get_key_ids(std::string const & pattern, std::vector & pubkeys); - void get_private_keys(std::vector & privkeys); - void get_public_keys(std::vector & pubkeys); bool public_key_exists(hexenc const & hash); bool public_key_exists(rsa_keypair_id const & id); - bool private_key_exists(rsa_keypair_id const & id); void get_pubkey(hexenc const & hash, @@ -339,10 +336,6 @@ void put_key(rsa_keypair_id const & id, base64 const & pub_encoded); - void get_key(rsa_keypair_id const & id, - base64< arc4 > & priv_encoded); - void delete_private_key(rsa_keypair_id const & pub_id); - void delete_public_key(rsa_keypair_id const & pub_id); // note: this section is ridiculous. please do something about it. ======================================================================== --- key_store.cc 05ee19ff4146a3ef4b9b1751e5dbf39a55f26a50 +++ key_store.cc 12e5e98f0e37a8c9a48ac545358b9d8c316099e9 @@ -208,11 +208,22 @@ { maybe_read_key_dir(); L(F("putting key pair '%s'") % ident); - I(keys.insert(std::make_pair(ident, kp)).second); - hexenc hash; - key_hash_code(ident, kp.pub, hash); - I(hashes.insert(std::make_pair(hash, ident)).second); - write_key(ident); + std::pair::iterator, bool> res; + res = keys.insert(std::make_pair(ident, kp)); + if (res.second) + { + hexenc hash; + key_hash_code(ident, kp.pub, hash); + I(hashes.insert(std::make_pair(hash, ident)).second); + write_key(ident); + } + else + { + E(/*keys_match(ident, res.first->second.priv, ident, kp.priv) + && */keys_match(ident, res.first->second.pub, ident, kp.pub), + F("Cannot store key '%s'; a different key by that name exists.") + % ident); + } } void ======================================================================== --- keys.hh 37f54b83f95952ba438899513ca0991cc63d4d10 +++ keys.hh 969e5b32cfc2d56bfe239077a3104767d9ad72d7 @@ -80,11 +80,11 @@ base64 const & key1, rsa_keypair_id const & id2, base64 const & key2); - +/* Doesn't work bool keys_match(rsa_keypair_id const & id1, base64< rsa_priv_key > const & key1, rsa_keypair_id const & id2, base64< rsa_priv_key > const & key2); +*/ - #endif // __KEYS_HH__ ======================================================================== --- schema.sql 513edf45fe7238d23513b7062d965000271e2d67 +++ schema.sql d25e530b103ebfafad30760aeac1dc01393e15f1 @@ -71,13 +71,6 @@ keydata not null -- RSA public params ); -CREATE TABLE private_keys - ( - hash not null unique, -- hash of remaining fields separated by ":" - id primary key, -- as in public_keys (same identifiers, in fact) - keydata not null -- encrypted RSA private params - ); - CREATE TABLE manifest_certs ( hash not null unique, -- hash of remaining fields separated by ":" ======================================================================== --- schema_migration.cc 572aa9fa72d4f32c72120d2792b8e27961ffa610 +++ schema_migration.cc 6c6c942ba5f4bfafe82145ea516c57dc4a3c8b39 @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -18,6 +19,7 @@ #include "schema_migration.hh" #include "botan/botan.h" #include "app_state.hh" +#include "keys.hh" // this file knows how to migrate schema databases. the general strategy is // to hash each schema we ever use, and make a list of the SQL commands @@ -777,6 +779,63 @@ return true; } +static int +extract_key(void *ptr, int ncols, char **values, char **names) +{ + // This is stupid. The cast should not be needed. + map *out = (map*)ptr; + I(ncols == 2); + out->insert(make_pair(string(values[0]), string(values[1]))); + return 0; +} +static bool +migrate_client_to_external_privkeys(sqlite3 * sql, + char ** errmsg, + app_state *app) +{ + int res; + map pub, priv; + vector pairs; + + res = sqlite3_exec(sql, + "SELECT id, keydata FROM private_keys;", + &extract_key, &priv, errmsg); + if (res != SQLITE_OK) + return false; + + res = sqlite3_exec(sql, + "SELECT id, keydata FROM public_keys;", + &extract_key, &pub, errmsg); + if (res != SQLITE_OK) + return false; + + for (map::const_iterator i = priv.begin(); + i != priv.end(); ++i) + { + rsa_keypair_id ident = i->first; + base64< arc4 > old_priv = i->second; + map::const_iterator j = pub.find(i->first); + keypair kp; + migrate_private_key(*app, ident, old_priv, kp); + MM(kp.pub); + if (j != pub.end()) + { + base64< rsa_pub_key > pub = j->second; + MM(pub); + N(keys_match(ident, pub, ident, kp.pub), + F("public and private keys for %s don't match") % ident); + } + + app->keys.put_key_pair(ident, kp); + } + + res = sqlite3_exec(sql, "DROP TABLE private_keys;", NULL, NULL, errmsg); + if (res != SQLITE_OK) + return false; + + return true; +} + void migrate_monotone_schema(sqlite3 *sql, app_state *app) { @@ -802,9 +861,12 @@ m.add("e372b508bea9b991816d1c74680f7ae10d2a6d94", &migrate_client_to_add_indexes); + m.add("1509fd75019aebef5ac3da3a5edf1312393b70e9", + &migrate_client_to_external_privkeys); + // IMPORTANT: whenever you modify this to add a new schema version, you must // also add a new migration test for the new schema version. See // tests/t_migrate_schema.at for details. - m.migrate(sql, "1509fd75019aebef5ac3da3a5edf1312393b70e9"); + m.migrate(sql, "bd86f9a90b5d552f0be1fa9aee847ea0f317778b"); } ======================================================================== --- tests/t_migrate_schema.at 2074b20ad00853b9490ce370b6e6002bf2fe52f2 +++ tests/t_migrate_schema.at be3ab9479b942b36b7b997abfa21f47e7457d78b @@ -13,8 +13,8 @@ # should be good enough. # This means that every time the database schema is changed, you need -# to add a new piece to this test. The way you do this is to run this -# test with the -d option, like so: +# to add a new piece to this test, for the new schema. The way you do +# this is to run this test with the -d option, like so: # $ ./testsuite AUTOTEST_PATH=. -d 73 # this will cause autotest to leave behind the temporary files the # test generates. You want 'testsuite.dir/073/latest.db.dump'. Gzip @@ -31,32 +31,33 @@ # We don't want the standard db, we want full control ourselves AT_CHECK(rm -f test.db) +AT_CHECK(rm -r keys/) AT_CHECK(MONOTONE db init) # Put some random keys in, with and without corresponding private keys -AT_DATA(migrate_keys, [@<:@pubkey address@hidden@:>@ -MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCfN/cAMabgb6T7m8ksGnpQ7LO6hOdnc/7V -yivrRGtmpwSItljht1bmgLQF37KiSPoMEDUb1stfKxaMsYiy8iTyoQ+M2EVFP37n2rtnNZ0H -oVcQd2sRsCerQFh9nslRPymlkQXUlOiNFN6RlFNcdjkucqNe+YorFX21EYw7XuT5XwIBEQ== -@<:@end@:>@ -@<:@pubkey address@hidden@:>@ +AT_DATA(migrate_keys, [@<:@pubkey address@hidden@:>@ MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC54vVjrrqYoTfPTgWm6JpuL+kOERcN2OSc BsWq6cb4Wm3nlymwVqJJywq6cbfygUYwmqyiRLPxRosfLGu228AhEzaM4JbAH1pgg7CwvvVd fHRXNAXEMgO89gBjkkecxLi4U/T67DrLjkRPAilCgWLZNv8YeOG9XAPegWyr7hNA9wIBEQ== @<:@end@:>@ -@<:@privkey address@hidden@:>@ -npy0jyqbZdylFkMjdR9OvlqmDHuBGXpGFPt94h96aG+Lp+OdBCmWx8GueHk8FKkexwPqhRBM -PPopUeuwqxuSX+yEodMl5IHBmin0nLnbOeBjtasjemBFEmdNl/jPDF/AeQ2WHhanB731dSQc -vzLOQfKAYmfk56PPULi9oJJNUxaCkvsWtvaI+HUHZrjyyV7dA4Vsc6Jvx2Yf1+MdDAEGk/Rw -ZtP0LmuoiyvRDFqBF8aTmnotyb4kRKohnJ7VF+y6hYvmtMpM3TKnpR7EbojBzNPqKuO7nPUz -jGxA7F84O24Vbf128PNSI5vj4istow26aPjn28qPjfRrkV30WLL/dXfYJkfkTqglYnoEXvF/ -xZoVxxNeAX58mgy0A1ErVxv8U7TwuP983GHEpLwy3gbiP+9akAJCr8r823DHmQqq5QDELibP -cuXZfOttpfVRkcbMhjeF0M6czc4HoKgHTAnf/18hzdZwGX/WWvRIBHImbUJ+mDbp2ByDTfKf -ErGXSvZ3HxCqBD8yx1SnXhV8IDHaBmV9wwYcN+H2cxOWGZk7g7xJS19+a3UQB3c3sSXQVJBp -6QpCZgysxkZwzuXDzzLZPT9SLZz4K2p7+7BwMbpy9ZxcyAzmiEtpA24UP06jtjFN7WcXAdx/ -E5Gmoe9b1EiXWdReHjUGpc6k0LQ0PPXAwqrcGdwYbOLDZ5xsQ5AsEYSFtyTS60D1nHBcdNmW -M0eOUJFdJf/uNe/2EApc3a8TyEkZtVqiYtOVV3qDB9NmU4bVOkDqzl1F7zJwATWbmasSdkM3 -6lxDkczBfCrEjH5p5Y8DU+ge4e4LRtknY9oBOJ7EQO0twYJg3k0= +@<:@keypair address@hidden@:>@ +MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCfN/cAMabgb6T7m8ksGnpQ7LO6hOdnc/7V +yivrRGtmpwSItljht1bmgLQF37KiSPoMEDUb1stfKxaMsYiy8iTyoQ+M2EVFP37n2rtnNZ0H +oVcQd2sRsCerQFh9nslRPymlkQXUlOiNFN6RlFNcdjkucqNe+YorFX21EYw7XuT5XwIBEQ==# +MIICyTBDBgkqhkiG9w0BBQ0wNjAeBgkqhkiG9w0BBQwwEQQIefgT/1vcuRoCAggAAgEYMBQG +CCqGSIb3DQMHBAjBYTCc+TuEIQSCAoDbSxK5UeaBREeMlP8ZBFihsxyapmrUs/ZYbieJIq+j +ZQJ+OX15hzpbk2/jqfUgYaV7uFBf8JVglWLw6SfQe3KrvFEH1K3FsIIVf2SzEvERiWUd9YuT +P6pxTwT0zUbyiKQJl+43BSYb8UWRBOsuAAJXUJ1KPRSd9BYSvysmc1CSJd+6TKsxCSH+3bFx +gH07FOzE/Q20bv8duk8AcM+9X/0Ob8hfW8Nt5/QSrc/fdutMKPrJOIaQRvOa9wulXbbmn28E +uQd8+21t22QvMPif/39VwvbDpX6a77Rg1ZOL0o2pFvpObmMnwTMAKq6ayJU+qhNpXh5T5HOw +8jUYt5GU2YCaxMczFvJousYi+5lN+rldwIxMyx9Z3pFFnbxYI5n9VuJUnqz0ZlCsf785/NXy +gZaYt7HCKgnCw4jUEK9aAqHIzsJCNOvM60dW92ZyhU6iycs9uzlW87hWC3mwEG518JShdAyX +hk0LhprJd4OEj/LJarLaTveePkFWJm4XyP5R+ByJgWOVqPdn7ILl2rRRlmpIsyTzBisOf/Aw +DF3oO2zlOPSCtPgVblQnUujuhrWy4/uldwSQC+78klzmcNG4z6UTDkThUiAKMoT27z9AOR3a +qv7e+C2ExR7ykk9lRz4836i4wMJddw5d1+AARpxGjidw5FjaTRss6NLB0k9Wo7fChFIlEjYc +N+BGMiMbkGiXLFtATQKEDqx/kDAlecN0FpljC5g6x6FYHxouaulEY0L7RkYSfVUvKQU2r6Lu +vTlEEZeNPdRtYwOz4ogqysHgZzDmbJ/AOt23u5R+O+vipNaYpv0S/vlGMJPtj2TcpgIn2ooZ +KLqSDA8igs89M3oRnyvz @<:@end@:>@ ]) AT_CHECK(MONOTONE read < migrate_keys, [], [ignore], [ignore]) @@ -703,4 +704,137 @@ /+m45zWE3z5fmXhj/OdLCq+/fk5kaJps/cfmfwBo9BCXWD4AAA== ]) +CHECK_MIGRATE_FROM([bd86f9a90b5d552f0be1fa9aee847ea0f317778b], +[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 +/nP1X/ane51vOQAA +]) + AT_CLEANUP