// Copyright (C) 2002 Graydon Hoare // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. // // This program is distributed WITHOUT ANY WARRANTY; without even the // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. #include "base.hh" #include #include #include #include #include #include #include "botan/botan.h" #include "botan/rsa.h" #include "botan/keypair.h" #include "botan/pem.h" #include "constants.hh" #include "keys.hh" #include "lua_hooks.hh" #include "netio.hh" #include "platform.hh" #include "safe_map.hh" #include "transforms.hh" #include "simplestring_xform.hh" #include "sanity.hh" #include "ui.hh" #include "cert.hh" #include "app_state.hh" #include "charset.hh" #include "ssh_agent.hh" using std::cout; using std::make_pair; using std::map; using std::string; using std::vector; using boost::scoped_ptr; using boost::shared_ptr; using boost::shared_dynamic_cast; using Botan::byte; using Botan::get_cipher; using Botan::PKCS8_PrivateKey; using Botan::PK_Decryptor; using Botan::PK_Encryptor; using Botan::PK_Signer; using Botan::PK_Verifier; using Botan::Pipe; using Botan::RSA_PrivateKey; using Botan::RSA_PublicKey; using Botan::SecureVector; using Botan::X509_PublicKey; // there will probably forever be bugs in this file. it's very // hard to get right, portably and securely. sorry about that. static void do_arc4(SecureVector & sym_key, SecureVector & payload) { L(FL("running arc4 process on %d bytes of data") % payload.size()); Pipe enc(get_cipher("ARC4", sym_key, Botan::ENCRYPTION)); enc.process_msg(payload); 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. void get_passphrase(lua_hooks & lua, rsa_keypair_id const & keyid, utf8 & phrase, bool confirm_phrase, bool force_from_user, bool generating_key) { // 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 = lua.hook_persist_phrase_ok(); static map phrases; if (!force_from_user && phrases.find(keyid) != phrases.end()) { phrase = phrases[keyid]; return; } string lua_phrase; if (!force_from_user && lua.hook_get_passphrase(keyid, lua_phrase)) { // 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")); } else { 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(); 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; } try { external ext_phrase(pass1); system_to_utf8(ext_phrase, phrase); // 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 generate_key_pair(lua_hooks & lua, // to hook for phrase rsa_keypair_id const & id, // to prompting user for phrase keypair & kp_out) { utf8 phrase; get_passphrase(lua, id, phrase, true, true, true); generate_key_pair(kp_out, phrase); } void generate_key_pair(keypair & kp_out, utf8 const phrase) { SecureVector pubkey, privkey; rsa_pub_key raw_pub_key; rsa_priv_key raw_priv_key; // generate private key (and encrypt it) RSA_PrivateKey priv(constants::keylen); Pipe p; p.start_msg(); if (phrase().length()) { Botan::PKCS8::encrypt_key(priv, p, phrase(), "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER); } else { Botan::PKCS8::encode(priv, p); } raw_priv_key = rsa_priv_key(p.read_all_as_string()); // generate public key Pipe p2; p2.start_msg(); Botan::X509::encode(priv, p2, Botan::RAW_BER); raw_pub_key = rsa_pub_key(p2.read_all_as_string()); // if all that worked, we can return our results to caller encode_base64(raw_priv_key, kp_out.priv); encode_base64(raw_pub_key, kp_out.pub); L(FL("generated %d-byte public key\n" "generated %d-byte (encrypted) private key\n") % kp_out.pub().size() % kp_out.priv().size()); } // ask for passphrase then decrypt a private key. shared_ptr get_private_key(lua_hooks & lua, rsa_keypair_id const & id, base64< rsa_priv_key > const & priv, bool force_from_user) { rsa_priv_key decoded_key; utf8 phrase; bool force = force_from_user; L(FL("base64-decoding %d-byte private key") % priv().size()); decode_base64(priv, decoded_key); shared_ptr pkcs8_key; try //with empty passphrase { Pipe p; p.process_msg(decoded_key()); pkcs8_key = shared_ptr(Botan::PKCS8::load_key(p, phrase())); } catch (...) { L(FL("failed to decrypt key with no passphrase")); } if (!pkcs8_key) { for (int i = 0; i < 3; ++i) { get_passphrase(lua, id, phrase, false, force); L(FL("have %d-byte encrypted private key") % decoded_key().size()); try { Pipe p; p.process_msg(decoded_key()); pkcs8_key = shared_ptr(Botan::PKCS8::load_key(p, phrase())); break; } catch (...) { if (i >= 2) throw informative_failure("failed to decrypt private RSA key, " "probably incorrect passphrase"); // don't use the cached bad one next time force = true; continue; } } } if (pkcs8_key) { shared_ptr priv_key; priv_key = shared_dynamic_cast(pkcs8_key); if (!priv_key) throw informative_failure("Failed to get RSA signing key"); return priv_key; } I(false); } // converts an oldstyle arc4 encrypted key into a newstyle pkcs#8 encoded // key. the public key is also included void migrate_private_key(app_state & app, rsa_keypair_id const & id, base64< arc4 > const & old_priv, keypair & new_kp) { arc4 decoded_key; SecureVector decrypted_key; utf8 phrase; bool force = false; // need to decrypt the old key shared_ptr priv_key; L(FL("base64-decoding %d-byte old private key") % old_priv().size()); decode_base64(old_priv, decoded_key); for (int i = 0; i < 3; ++i) { decrypted_key.set(reinterpret_cast(decoded_key().data()), decoded_key().size()); get_passphrase(app.lua, id, phrase, false, force); SecureVector sym_key; sym_key.set(reinterpret_cast(phrase().data()), phrase().size()); do_arc4(sym_key, decrypted_key); L(FL("building signer from %d-byte decrypted private key") % decrypted_key.size()); shared_ptr pkcs8_key; try { Pipe p; p.process_msg(Botan::PEM_Code::encode(decrypted_key, "PRIVATE KEY")); pkcs8_key = shared_ptr(Botan::PKCS8::load_key(p)); } catch (...) { if (i >= 2) throw informative_failure("failed to decrypt old private RSA key, " "probably incorrect passphrase"); // don't use the cache bad one next time force = true; continue; } priv_key = shared_dynamic_cast(pkcs8_key); if (!priv_key) throw informative_failure("Failed to get old RSA key"); } I(priv_key); // now we can write out the new key Pipe p; p.start_msg(); Botan::PKCS8::encrypt_key(*priv_key, p, phrase(), "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER); rsa_priv_key raw_priv = rsa_priv_key(p.read_all_as_string()); encode_base64(raw_priv, new_kp.priv); // also the public portion Pipe p2; p2.start_msg(); Botan::X509::encode(*priv_key, p2, Botan::RAW_BER); rsa_pub_key raw_pub = rsa_pub_key(p2.read_all_as_string()); encode_base64(raw_pub, new_kp.pub); } void change_key_passphrase(lua_hooks & lua, rsa_keypair_id const & id, base64< rsa_priv_key > & encoded_key) { shared_ptr priv = get_private_key(lua, id, encoded_key, true); utf8 new_phrase; get_passphrase(lua, id, new_phrase, true, true); Pipe p; p.start_msg(); Botan::PKCS8::encrypt_key(*priv, p, new_phrase(), "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER); rsa_priv_key decoded_key = rsa_priv_key(p.read_all_as_string()); encode_base64(decoded_key, encoded_key); } void make_signature(app_state & app, // to hook for phrase rsa_keypair_id const & id, // to prompting user for phrase base64< rsa_priv_key > const & priv, string const & tosign, base64 & signature) { E(!app.opts.ssh_sign.empty(), F("--ssh-sign requires a value ['yes', 'no', 'only', or 'check']")); E(app.opts.ssh_sign == "yes" || app.opts.ssh_sign == "no" || app.opts.ssh_sign == "check" || app.opts.ssh_sign == "only", F("--ssh-sign must be set to 'yes', 'no', 'only', or 'check'")); keypair key; app.keys.get_key_pair(id, key); string sig_string; //sign with ssh-agent (if connected) N(app.agent.connected() || app.opts.ssh_sign != "only", F("You have chosen to sign only with ssh-agent but ssh-agent" " does not seem to be running.")); if (app.opts.ssh_sign == "yes" || app.opts.ssh_sign == "check" || app.opts.ssh_sign == "only") { /* vector ssh_keys = app.agent.get_keys(); if (ssh_keys.size() <= 0) L(FL("make_signature: no rsa keys received from ssh-agent")); else { */ if (app.agent.connected()) { //grab the monotone public key as an RSA_PublicKey app.keys.get_key_pair(id, key); rsa_pub_key pub; decode_base64(key.pub, pub); SecureVector pub_block; pub_block.set(reinterpret_cast(pub().data()), pub().size()); L(FL("make_signature: building %d-byte pub key") % pub_block.size()); shared_ptr x509_key = shared_ptr(Botan::X509::load_key(pub_block)); shared_ptr pub_key = shared_dynamic_cast(x509_key); if (!pub_key) throw informative_failure("Failed to get monotone RSA public key"); /* //if monotone key matches ssh-agent key, sign with ssh-agent for (vector::const_iterator si = ssh_keys.begin(); si != ssh_keys.end(); ++si) { if ((*pub_key).get_e() == (*si).get_e() && (*pub_key).get_n() == (*si).get_n()) { L(FL("make_signature: ssh key matches monotone key, signing with" " ssh-agent")); */ app.agent.sign_data(*pub_key, tosign, sig_string); /* break; } } */ } if (sig_string.length() <= 0) L(FL("make_signature: monotone and ssh-agent keys do not match, will" " use monotone signing")); } string ssh_sig = sig_string; N(ssh_sig.length() > 0 || app.opts.ssh_sign != "only", F("You don't seem to have your monotone key imported ")); if (ssh_sig.length() <= 0 || app.opts.ssh_sign == "check" || app.opts.ssh_sign == "no") { SecureVector sig; // we permit the user to relax security here, by caching a decrypted key // (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 = (!app.signers.empty()) || app.lua.hook_persist_phrase_ok(); shared_ptr signer; shared_ptr priv_key; if (persist_phrase && app.signers.find(id) != app.signers.end()) signer = app.signers[id].first; else { priv_key = get_private_key(app.lua, id, priv); if (app.agent.connected() && app.opts.ssh_sign != "only" && app.opts.ssh_sign != "no") { L(FL("keys.cc: make_signature: adding private key (%s) to ssh-agent") % id()); app.agent.add_identity(*priv_key, id()); } signer = shared_ptr(get_pk_signer(*priv_key, "EMSA3(SHA-1)")); /* XXX This is ugly. We need to keep the key around as long * as the signer is around, but the shared_ptr for the key will go * away after we leave this scope. Hence we store a pair of * so they both exist. */ if (persist_phrase) app.signers.insert(make_pair(id,make_pair(signer,priv_key))); } sig = signer->sign_message(reinterpret_cast(tosign.data()), tosign.size()); sig_string = string(reinterpret_cast(sig.begin()), sig.size()); } if (app.opts.ssh_sign == "check" && ssh_sig.length() > 0) { E(ssh_sig == sig_string, F("make_signature: ssh signature (%i) != monotone signature (%i)\n" "ssh signature : %s\n" "monotone signature: %s") % ssh_sig.length() % sig_string.length() % encode_hexenc(ssh_sig) % encode_hexenc(sig_string)); L(FL("make_signature: signatures from ssh-agent and monotone" " are the same")); } L(FL("make_signature: produced %d-byte signature") % sig_string.size()); encode_base64(rsa_sha1_signature(sig_string), signature); E(check_signature(app, id, key.pub, tosign, signature), F("make_signature: signature is not valid")); } bool check_signature(app_state &app, rsa_keypair_id const & id, base64 const & pub_encoded, string const & alleged_text, base64 const & signature) { // examine pubkey bool persist_phrase = (!app.verifiers.empty()) || app.lua.hook_persist_phrase_ok(); shared_ptr verifier; shared_ptr pub_key; if (persist_phrase && app.verifiers.find(id) != app.verifiers.end()) verifier = app.verifiers[id].first; else { rsa_pub_key pub; decode_base64(pub_encoded, pub); SecureVector pub_block; pub_block.set(reinterpret_cast(pub().data()), pub().size()); L(FL("building verifier for %d-byte pub key") % pub_block.size()); shared_ptr x509_key = shared_ptr(Botan::X509::load_key(pub_block)); pub_key = shared_dynamic_cast(x509_key); if (!pub_key) throw informative_failure("Failed to get RSA verifying key"); verifier = shared_ptr(get_pk_verifier(*pub_key, "EMSA3(SHA-1)")); /* XXX This is ugly. We need to keep the key around * as long as the verifier is around, but the shared_ptr will go * away after we leave this scope. Hence we store a pair of * so they both exist. */ if (persist_phrase) app.verifiers.insert(make_pair(id, make_pair(verifier, pub_key))); } // examine signature rsa_sha1_signature sig_decoded; decode_base64(signature, sig_decoded); // check the text+sig against the key L(FL("checking %d-byte (%d decoded) signature") % signature().size() % sig_decoded().size()); bool valid_sig = verifier->verify_message( reinterpret_cast(alleged_text.data()), alleged_text.size(), reinterpret_cast(sig_decoded().data()), sig_decoded().size()); return valid_sig; } void encrypt_rsa(lua_hooks & lua, rsa_keypair_id const & id, base64 & pub_encoded, string const & plaintext, rsa_oaep_sha_data & ciphertext) { rsa_pub_key pub; decode_base64(pub_encoded, pub); SecureVector pub_block; pub_block.set(reinterpret_cast(pub().data()), pub().size()); shared_ptr x509_key = shared_ptr(Botan::X509::load_key(pub_block)); shared_ptr pub_key = shared_dynamic_cast(x509_key); if (!pub_key) throw informative_failure("Failed to get RSA encrypting key"); shared_ptr encryptor; encryptor = shared_ptr(get_pk_encryptor(*pub_key, "EME1(SHA-1)")); SecureVector ct; ct = encryptor->encrypt( reinterpret_cast(plaintext.data()), plaintext.size()); ciphertext = rsa_oaep_sha_data(string(reinterpret_cast(ct.begin()), ct.size())); } void decrypt_rsa(lua_hooks & lua, rsa_keypair_id const & id, base64< rsa_priv_key > const & priv, rsa_oaep_sha_data const & ciphertext, string & plaintext) { shared_ptr priv_key = get_private_key(lua, id, priv); shared_ptr decryptor; decryptor = shared_ptr(get_pk_decryptor(*priv_key, "EME1(SHA-1)")); SecureVector plain; plain = decryptor->decrypt( reinterpret_cast(ciphertext().data()), ciphertext().size()); plaintext = string(reinterpret_cast(plain.begin()), plain.size()); } 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) { data tdat(ident() + ":" + remove_ws(pub())); calculate_ident(tdat, out); } void key_hash_code(rsa_keypair_id const & ident, base64< rsa_priv_key > const & priv, hexenc & out) { data tdat(ident() + ":" + remove_ws(priv())); calculate_ident(tdat, out); } // helper to compare if two keys have the same hash // (ie are the same key) bool keys_match(rsa_keypair_id const & id1, base64 const & key1, rsa_keypair_id const & id2, base64 const & key2) { hexenc hash1, hash2; key_hash_code(id1, key1, hash1); key_hash_code(id2, key2, hash2); return hash1 == hash2; } 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) { hexenc hash1, hash2; key_hash_code(id1, key1, hash1); key_hash_code(id2, key2, hash2); return hash1 == hash2; } void require_password(rsa_keypair_id const & key, app_state & app) { N(priv_key_exists(app, key), F("no key pair '%s' found in key store '%s'") % key % app.keys.get_key_dir()); keypair kp; load_key_pair(app, key, kp); if (app.lua.hook_persist_phrase_ok()) { string plaintext("hi maude"); base64 sig; make_signature(app, key, kp.priv, plaintext, sig); N(check_signature(app, key, kp.pub, plaintext, sig), F("passphrase for '%s' is incorrect") % key); } } #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" UNIT_TEST(key, arc4) { string pt("new fascist tidiness regime in place"); string phr("still spring water"); SecureVector phrase(reinterpret_cast(phr.data()), phr.size()); SecureVector orig(reinterpret_cast(pt.data()), pt.size()); SecureVector data(orig); UNIT_TEST_CHECKPOINT("encrypting data"); do_arc4(phrase, data); UNIT_TEST_CHECK(data != orig); UNIT_TEST_CHECKPOINT("decrypting data"); do_arc4(phrase, data); UNIT_TEST_CHECK(data == orig); } UNIT_TEST(key, signature_round_trip) { app_state app; app.set_key_dir(system_path(get_current_working_dir()) / ".monotone_tmp" / "keys"); app.lua.add_std_hooks(); app.lua.add_test_hooks(); UNIT_TEST_CHECKPOINT("generating key pairs"); keypair kp; utf8 passphrase("bob123@example.com"); rsa_keypair_id key("bob123@example.com"); generate_key_pair(kp, passphrase); app.keys.put_key_pair(key, kp); UNIT_TEST_CHECKPOINT("signing plaintext"); string plaintext("test string to sign"); base64 sig; make_signature(app, key, kp.priv, plaintext, sig); UNIT_TEST_CHECKPOINT("checking signature"); UNIT_TEST_CHECK(check_signature(app, key, kp.pub, plaintext, sig)); string broken_plaintext = plaintext + " ...with a lie"; UNIT_TEST_CHECKPOINT("checking non-signature"); UNIT_TEST_CHECK(!check_signature(app, key, kp.pub, broken_plaintext, sig)); app.keys.delete_key(key); } #endif // BUILD_UNIT_TESTS // Local Variables: // mode: C++ // fill-column: 76 // c-file-style: "gnu" // indent-tabs-mode: nil // End: // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: