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.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.KeyCertificate; import net.i2p.data.PrivateKey; import net.i2p.data.PrivateKeyFile; import net.i2p.data.PublicKey; import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.util.EventLog; import net.i2p.util.Log; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SystemVersion; /** * Warning - misnamed. This creates a new RouterIdentity, i.e. * new router keys and hash. It then builds a new RouterInfo * and saves all the keys. This is generally run only once, on a new install. * */ public class CreateRouterInfoJob extends JobImpl { private final Log _log; private final Job _next; public static final String INFO_FILENAME = "router.info"; public static final String KEYS_FILENAME = "router.keys"; public static final String KEYS2_FILENAME = "router.keys.dat"; static final String PROP_ROUTER_SIGTYPE = "router.sigType"; /** TODO make everybody Ed */ private static final SigType DEFAULT_SIGTYPE = SystemVersion.isAndroid() ? SigType.DSA_SHA1 : SigType.EdDSA_SHA512_Ed25519; CreateRouterInfoJob(RouterContext ctx, Job next) { super(ctx); _next = next; _log = ctx.logManager().getLog(CreateRouterInfoJob.class); } public String getName() { return "Create New Router Info"; } public void runJob() { _log.debug("Creating the new router info"); // create a new router info and store it where LoadRouterInfoJob looks synchronized (getContext().router().routerInfoFileLock) { createRouterInfo(); } getContext().jobQueue().addJob(_next); } /** * Writes 6 files: router.info (standard RI format), * router.keys.dat, and 4 individual key files under keyBackup/ * * router.keys.dat file format: This is the * same "eepPriv.dat" format used by the client code, * as documented in PrivateKeyFile. * * Old router.keys file format: Note that this is NOT the * same "eepPriv.dat" format used by the client code. *<pre> * - Private key (256 bytes) * - Signing Private key (20 bytes) * - Public key (256 bytes) * - Signing Public key (128 bytes) * Total 660 bytes *</pre> * * Caller must hold Router.routerInfoFileLock. */ RouterInfo createRouterInfo() { SigType type = getSigTypeConfig(getContext()); RouterInfo info = new RouterInfo(); OutputStream fos1 = null; try { info.setAddresses(getContext().commSystem().createAddresses()); // not necessary, in constructor //info.setPeers(new HashSet()); info.setPublished(getCurrentPublishDate(getContext())); Object keypair[] = getContext().keyGenerator().generatePKIKeypair(); PublicKey pubkey = (PublicKey)keypair[0]; PrivateKey privkey = (PrivateKey)keypair[1]; SimpleDataStructure signingKeypair[] = getContext().keyGenerator().generateSigningKeys(type); SigningPublicKey signingPubKey = (SigningPublicKey)signingKeypair[0]; SigningPrivateKey signingPrivKey = (SigningPrivateKey)signingKeypair[1]; RouterIdentity ident = new RouterIdentity(); Certificate cert = createCertificate(getContext(), signingPubKey); ident.setCertificate(cert); ident.setPublicKey(pubkey); ident.setSigningPublicKey(signingPubKey); byte[] padding; int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length(); if (padLen > 0) { padding = new byte[padLen]; getContext().random().nextBytes(padding); ident.setPadding(padding); } else { padding = null; } info.setIdentity(ident); Properties stats = getContext().statPublisher().publishStatistics(ident.getHash()); info.setOptions(stats); info.sign(signingPrivKey); if (!info.isValid()) throw new DataFormatException("RouterInfo we just built is invalid: " + info); // remove router.keys (new File(getContext().getRouterDir(), KEYS_FILENAME)).delete(); // write router.info File ifile = new File(getContext().getRouterDir(), INFO_FILENAME); fos1 = new BufferedOutputStream(new SecureFileOutputStream(ifile)); info.writeBytes(fos1); // write router.keys.dat File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME); PrivateKeyFile pkf = new PrivateKeyFile(kfile, pubkey, signingPubKey, cert, privkey, signingPrivKey, padding); pkf.write(); // set or overwrite old random keys Map<String, String> map = new HashMap<String, String>(2); byte rk[] = new byte[32]; getContext().random().nextBytes(rk); map.put(Router.PROP_IB_RANDOM_KEY, Base64.encode(rk)); getContext().random().nextBytes(rk); map.put(Router.PROP_OB_RANDOM_KEY, Base64.encode(rk)); getContext().router().saveConfig(map, null); getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); if (_log.shouldLog(Log.INFO)) _log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]"); getContext().router().eventLog().addEvent(EventLog.REKEYED, ident.calculateHash().toBase64()); } catch (GeneralSecurityException gse) { _log.log(Log.CRIT, "Error building the new router information", gse); } catch (DataFormatException dfe) { _log.log(Log.CRIT, "Error building the new router information", dfe); } catch (IOException ioe) { _log.log(Log.CRIT, "Error writing out the new router information", ioe); } finally { if (fos1 != null) try { fos1.close(); } catch (IOException ioe) {} } return info; } /** * The configured SigType to expect on read-in * @since 0.9.16 */ public static SigType getSigTypeConfig(RouterContext ctx) { SigType cstype = DEFAULT_SIGTYPE; String sstype = ctx.getProperty(PROP_ROUTER_SIGTYPE); if (sstype != null) { SigType ntype = SigType.parseSigType(sstype); if (ntype != null) cstype = ntype; } // fallback? if (cstype != SigType.DSA_SHA1 && !cstype.isAvailable()) cstype = SigType.DSA_SHA1; return cstype; } /** * We probably don't want to expose the exact time at which a router published its info. * perhaps round down to the nearest minute? 10 minutes? 30 minutes? day? * */ static long getCurrentPublishDate(RouterContext context) { //_log.info("Setting published date to /now/"); return context.clock().now(); } /** * Only called at startup via LoadRouterInfoJob and RebuildRouterInfoJob. * Not called by periodic RepublishLocalRouterInfoJob. * We don't want to change the cert on the fly as it changes the router hash. * RouterInfo.isHidden() checks the capability, but RouterIdentity.isHidden() checks the cert. * There's no reason to ever add a hidden cert? * * @return the certificate for a new RouterInfo - probably a null cert. * @since 0.9.16 moved from Router */ static Certificate createCertificate(RouterContext ctx, SigningPublicKey spk) { if (spk.getType() != SigType.DSA_SHA1) return new KeyCertificate(spk); if (ctx.getBooleanProperty(Router.PROP_HIDDEN)) return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null); return Certificate.NULL_CERT; } }