package net.i2p.router.startup; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.SigType; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.data.router.RouterPrivateKeyFile; import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.crypto.FamilyKeyCrypto; import net.i2p.router.networkdb.kademlia.PersistentDataStore; import net.i2p.util.Log; /** * Run once or twice at startup by StartupJob, * and then runs BootCommSystemJob */ class LoadRouterInfoJob extends JobImpl { private final Log _log; private RouterInfo _us; private static final AtomicBoolean _keyLengthChecked = new AtomicBoolean(); public LoadRouterInfoJob(RouterContext ctx) { super(ctx); _log = ctx.logManager().getLog(LoadRouterInfoJob.class); } public String getName() { return "Load Router Info"; } public void runJob() { synchronized (getContext().router().routerInfoFileLock) { loadRouterInfo(); } if (_us == null) { RebuildRouterInfoJob r = new RebuildRouterInfoJob(getContext()); r.rebuildRouterInfo(false); // run a second time getContext().jobQueue().addJob(this); return; } else { getContext().router().setRouterInfo(_us); getContext().messageHistory().initialize(true); getContext().jobQueue().addJob(new BootCommSystemJob(getContext())); } } /** * Loads router.info and either router.keys.dat or router.keys. * * See CreateRouterInfoJob for file formats */ private void loadRouterInfo() { RouterInfo info = null; File rif = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME); boolean infoExists = rif.exists(); File rkf = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS_FILENAME); boolean keysExist = rkf.exists(); File rkf2 = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME); boolean keys2Exist = rkf2.exists(); InputStream fis1 = null; try { // if we have a routerinfo but no keys, things go bad in a hurry: // CRIT ...rkdb.PublishLocalRouterInfoJob: Internal error - signing private key not known? rescheduling publish for 30s // CRIT net.i2p.router.Router : Internal error - signing private key not known? Impossible? // CRIT ...sport.udp.EstablishmentManager: Error in the establisher java.lang.NullPointerException // at net.i2p.router.transport.udp.PacketBuilder.buildSessionConfirmedPacket(PacketBuilder.java:574) // so pretend the RI isn't there if there is no keyfile if (infoExists && (keys2Exist || keysExist)) { fis1 = new BufferedInputStream(new FileInputStream(rif)); info = new RouterInfo(); info.readBytes(fis1); // Catch this here before it all gets worse if (!info.isValid()) throw new DataFormatException("Our RouterInfo has a bad signature"); if (_log.shouldLog(Log.DEBUG)) _log.debug("Reading in routerInfo from " + rif.getAbsolutePath() + " and it has " + info.getAddresses().size() + " addresses"); // don't reuse if family name changed if (DataHelper.eq(info.getOption(FamilyKeyCrypto.OPT_NAME), getContext().getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME))) { _us = info; } else { _log.logAlways(Log.WARN, "NetDb family name changed"); } } if (keys2Exist || keysExist) { KeyData kd = readKeyData(rkf, rkf2); PublicKey pubkey = kd.routerIdentity.getPublicKey(); SigningPublicKey signingPubKey = kd.routerIdentity.getSigningPublicKey(); PrivateKey privkey = kd.privateKey; SigningPrivateKey signingPrivKey = kd.signingPrivateKey; SigType stype = signingPubKey.getType(); // check if the sigtype config changed SigType cstype = CreateRouterInfoJob.getSigTypeConfig(getContext()); boolean sigTypeChanged = stype != cstype; if (sigTypeChanged && getContext().getProperty(CreateRouterInfoJob.PROP_ROUTER_SIGTYPE) == null) { // Not explicitly configured, and default has changed // Give a 25% chance of rekeying for each restart // TODO reduce to ~3 (i.e. increase probability) in future release if (getContext().random().nextInt(4) > 0) { sigTypeChanged = false; if (_log.shouldWarn()) _log.warn("Deferring RI rekey from " + stype + " to " + cstype); } } if (sigTypeChanged || shouldRebuild(privkey)) { if (_us != null) { Hash h = _us.getIdentity().getHash(); _log.logAlways(Log.WARN, "Deleting old router identity " + h.toBase64()); // the netdb hasn't started yet, but we want to delete the RI File f = PersistentDataStore.getRouterInfoFile(getContext(), h); f.delete(); // the banlist can be called at any time getContext().banlist().banlistRouterForever(h, "Our previous identity"); _us = null; } if (sigTypeChanged) _log.logAlways(Log.WARN, "Rebuilding RouterInfo with new signature type " + cstype); // windows... close before deleting if (fis1 != null) { try { fis1.close(); } catch (IOException ioe) {} fis1 = null; } rif.delete(); rkf.delete(); rkf2.delete(); return; } getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); } } catch (IOException ioe) { _log.log(Log.CRIT, "Error reading the router info from " + rif.getAbsolutePath() + " and the keys from " + rkf.getAbsolutePath(), ioe); _us = null; // windows... close before deleting if (fis1 != null) { try { fis1.close(); } catch (IOException ioe2) {} fis1 = null; } rif.delete(); rkf.delete(); rkf2.delete(); } catch (DataFormatException dfe) { _log.log(Log.CRIT, "Corrupt router info or keys at " + rif.getAbsolutePath() + " / " + rkf.getAbsolutePath(), dfe); _us = null; // windows... close before deleting if (fis1 != null) { try { fis1.close(); } catch (IOException ioe) {} fis1 = null; } rif.delete(); rkf.delete(); rkf2.delete(); } finally { if (fis1 != null) try { fis1.close(); } catch (IOException ioe) {} } } /** * Does our RI private key length match the configuration? * If not, return true. * @since 0.9.8 */ private boolean shouldRebuild(PrivateKey privkey) { // Prevent returning true more than once, ever. // If we are called a second time, it's probably because we failed // to delete router.keys for some reason. if (!_keyLengthChecked.compareAndSet(false, true)) return false; byte[] pkd = privkey.getData(); boolean haslong = false; for (int i = 0; i < 8; i++) { if (pkd[i] != 0) { haslong = true; break; } } boolean uselong = getContext().keyGenerator().useLongElGamalExponent(); // transition to a longer key (update to 0.9.8) if (uselong && !haslong) _log.logAlways(Log.WARN, "Rebuilding RouterInfo with longer key"); // transition to a shorter key, should be rare (copy files to different hardware, // jbigi broke, user overrides in advanced config, ...) if (!uselong && haslong) _log.logAlways(Log.WARN, "Rebuilding RouterInfo with faster key"); return uselong != haslong; } /** @since 0.9.16 */ public static class KeyData { public final RouterIdentity routerIdentity; public final PrivateKey privateKey; public final SigningPrivateKey signingPrivateKey; public KeyData(RouterIdentity ri, PrivateKey pk, SigningPrivateKey spk) { routerIdentity = ri; privateKey = pk; signingPrivateKey = spk; } } /** * @param rkf1 in router.keys format, tried second * @param rkf2 in eepPriv.dat format, tried first * @return non-null, throws IOE if neither exisits * @since 0.9.16 */ public static KeyData readKeyData(File rkf1, File rkf2) throws DataFormatException, IOException { RouterIdentity ri; PrivateKey privkey; SigningPrivateKey signingPrivKey; if (rkf2.exists()) { RouterPrivateKeyFile pkf = new RouterPrivateKeyFile(rkf2); ri = pkf.getRouterIdentity(); if (!pkf.validateKeyPairs()) throw new DataFormatException("Key pairs invalid"); privkey = pkf.getPrivKey(); signingPrivKey = pkf.getSigningPrivKey(); } else { InputStream fis = null; try { fis = new BufferedInputStream(new FileInputStream(rkf1)); privkey = new PrivateKey(); privkey.readBytes(fis); signingPrivKey = new SigningPrivateKey(); signingPrivKey.readBytes(fis); PublicKey pubkey = new PublicKey(); pubkey.readBytes(fis); SigningPublicKey signingPubKey = new SigningPublicKey(); signingPubKey.readBytes(fis); // validate try { if (!pubkey.equals(KeyGenerator.getPublicKey(privkey))) throw new DataFormatException("Key pairs invalid"); if (!signingPubKey.equals(KeyGenerator.getSigningPublicKey(signingPrivKey))) throw new DataFormatException("Key pairs invalid"); } catch (IllegalArgumentException iae) { throw new DataFormatException("Key pairs invalid", iae); } ri = new RouterIdentity(); ri.setPublicKey(pubkey); ri.setSigningPublicKey(signingPubKey); ri.setCertificate(Certificate.NULL_CERT); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) {} } } return new KeyData(ri, privkey, signingPrivKey); } }