diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
index d525cdccd6..b669f1447c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
@@ -415,7 +415,9 @@ public class NetDbRenderer {
// shouldnt happen
buf.append("" + _("Published") + ": in ").append(DataHelper.formatDuration2(0-age)).append("???
\n");
}
- buf.append("" + _("Address(es)") + ": ");
+ buf.append("").append(_("Signing Key")).append(": ")
+ .append(info.getIdentity().getSigningPublicKey().getType().toString());
+ buf.append("
\n" + _("Address(es)") + ": ");
String country = _context.commSystem().getCountry(info.getIdentity().getHash());
if(country != null) {
buf.append("
+ * - Magic "I2Pkeys2"
+ * - 2 byte crypto type, always 0000 for now
+ * - 2 byte sig type, see SigType
+ * - 28 bytes unused
+ * - Private key (256 bytes)
+ * - Signing Private key (20 bytes or see SigType)
+ * - Public key (256 bytes)
+ * - Random padding for Signing Public Key if less than 128 bytes
+ * - Signing Public key (128 bytes or see SigTpe)
+ * Total 660 bytes
+ *
+ *
+ * Old router.keys file format: Note that this is NOT the
* same "eepPriv.dat" format used by the client code.
*
* - Private key (256 bytes) @@ -74,6 +103,7 @@ public class CreateRouterInfoJob extends JobImpl { * Caller must hold Router.routerInfoFileLock. */ RouterInfo createRouterInfo() { + SigType type = getSigTypeConfig(getContext()); RouterInfo info = new RouterInfo(); OutputStream fos1 = null; OutputStream fos2 = null; @@ -86,21 +116,26 @@ public class CreateRouterInfoJob extends JobImpl { // not necessary, in constructor //info.setPeers(new HashSet()); info.setPublished(getCurrentPublishDate(getContext())); - RouterIdentity ident = new RouterIdentity(); - Certificate cert = getContext().router().createCertificate(); - ident.setCertificate(cert); - PublicKey pubkey = null; - PrivateKey privkey = null; - SigningPublicKey signingPubKey = null; - SigningPrivateKey signingPrivKey = null; Object keypair[] = getContext().keyGenerator().generatePKIKeypair(); - pubkey = (PublicKey)keypair[0]; - privkey = (PrivateKey)keypair[1]; - Object signingKeypair[] = getContext().keyGenerator().generateSigningKeypair(); - signingPubKey = (SigningPublicKey)signingKeypair[0]; - signingPrivKey = (SigningPrivateKey)signingKeypair[1]; + 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); info.sign(signingPrivKey); @@ -108,23 +143,35 @@ public class CreateRouterInfoJob extends JobImpl { if (!info.isValid()) throw new DataFormatException("RouterInfo we just built is invalid: " + info); - String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); - File ifile = new File(getContext().getRouterDir(), infoFilename); + // 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); - String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); - File kfile = new File(getContext().getRouterDir(), keyFilename); + // write router.keys2 + File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME); fos2 = new BufferedOutputStream(new SecureFileOutputStream(kfile)); + fos2.write(KEYS2_MAGIC); + DataHelper.writeLong(fos2, 2, 0); + DataHelper.writeLong(fos2, 2, type.getCode()); + fos2.write(new byte[KEYS2_UNUSED_BYTES]); privkey.writeBytes(fos2); signingPrivKey.writeBytes(fos2); pubkey.writeBytes(fos2); + if (padding != null) + fos2.write(padding); signingPubKey.writeBytes(fos2); getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); - _log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]"); + 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) { @@ -136,6 +183,20 @@ public class CreateRouterInfoJob extends JobImpl { return info; } + /** + * The configured SigType to expect on read-in + * @since 0.9.16 + */ + public static SigType getSigTypeConfig(RouterContext ctx) { + SigType cstype = CreateRouterInfoJob.DEFAULT_SIGTYPE; + String sstype = ctx.getProperty(PROP_ROUTER_SIGTYPE); + if (sstype != null) { + SigType ntype = SigType.parseSigType(sstype); + if (ntype != null && ntype.isAvailable()) + cstype = ntype; + } + return cstype; + } /** * We probably don't want to expose the exact time at which a router published its info. @@ -146,4 +207,22 @@ public class CreateRouterInfoJob extends JobImpl { //_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; + } } diff --git a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java index 016b77bc33..4b5ffdf4fb 100644 --- a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java @@ -15,7 +15,9 @@ import java.io.InputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; +import net.i2p.crypto.SigType; import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.router.RouterInfo; @@ -26,7 +28,11 @@ import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; -public class LoadRouterInfoJob extends JobImpl { +/** + * 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(); @@ -45,6 +51,7 @@ public class LoadRouterInfoJob extends JobImpl { if (_us == null) { RebuildRouterInfoJob r = new RebuildRouterInfoJob(getContext()); r.rebuildRouterInfo(false); + // run a second time getContext().jobQueue().addJob(this); return; } else { @@ -54,15 +61,19 @@ public class LoadRouterInfoJob extends JobImpl { } } + /** + * Loads router.info and router.keys2 or router.keys. + * + * See CreateRouterInfoJob for file formats + */ private void loadRouterInfo() { - String routerInfoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); RouterInfo info = null; - String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); - - File rif = new File(getContext().getRouterDir(), routerInfoFile); + File rif = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME); boolean infoExists = rif.exists(); - File rkf = new File(getContext().getRouterDir(), keyFilename); + 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; InputStream fis2 = null; @@ -73,7 +84,7 @@ public class LoadRouterInfoJob extends JobImpl { // 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 && keysExist) { + if (infoExists && (keys2Exist || keysExist)) { fis1 = new BufferedInputStream(new FileInputStream(rif)); info = new RouterInfo(); info.readBytes(fis1); @@ -85,11 +96,37 @@ public class LoadRouterInfoJob extends JobImpl { _us = info; } - if (keysExist) { - fis2 = new BufferedInputStream(new FileInputStream(rkf)); + if (keys2Exist || keysExist) { + SigType stype; + if (keys2Exist) { + fis2 = new BufferedInputStream(new FileInputStream(rkf2)); + // read keys2 headers + byte[] magic = new byte[CreateRouterInfoJob.KEYS2_MAGIC.length]; + DataHelper.read(fis2, magic); + if (!DataHelper.eq(magic, CreateRouterInfoJob.KEYS2_MAGIC)) + throw new IOException("Bad magic"); + int ctype = (int) DataHelper.readLong(fis2, 2); + if (ctype != 0) + throw new IOException("Unsupported RI crypto type " + ctype); + int sstype = (int) DataHelper.readLong(fis2, 2); + stype = SigType.getByCode(sstype); + if (stype == null || !stype.isAvailable()) + throw new IOException("Unsupported RI sig type " + stype); + DataHelper.skip(fis2, CreateRouterInfoJob.KEYS2_UNUSED_BYTES); + } else { + fis2 = new BufferedInputStream(new FileInputStream(rkf)); + stype = SigType.DSA_SHA1; + } + + // check if the sigtype config changed + SigType cstype = CreateRouterInfoJob.getSigTypeConfig(getContext()); + boolean sigTypeChanged = stype != cstype; + PrivateKey privkey = new PrivateKey(); privkey.readBytes(fis2); - if (shouldRebuild(privkey)) { + if (sigTypeChanged || shouldRebuild(privkey)) { + if (sigTypeChanged) + _log.logAlways(Log.WARN, "Rebuilding RouterInfo with new signature type " + cstype); _us = null; // windows... close before deleting if (fis1 != null) { @@ -100,13 +137,19 @@ public class LoadRouterInfoJob extends JobImpl { fis2 = null; rif.delete(); rkf.delete(); + rkf2.delete(); return; } - SigningPrivateKey signingPrivKey = new SigningPrivateKey(); + SigningPrivateKey signingPrivKey = new SigningPrivateKey(stype); signingPrivKey.readBytes(fis2); PublicKey pubkey = new PublicKey(); pubkey.readBytes(fis2); - SigningPublicKey signingPubKey = new SigningPublicKey(); + SigningPublicKey signingPubKey = new SigningPublicKey(stype); + int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length(); + if (padLen > 0) { + // we lose the padding as keymanager doesn't store it, what to do? + DataHelper.skip(fis2, padLen); + } signingPubKey.readBytes(fis2); getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); @@ -125,6 +168,7 @@ public class LoadRouterInfoJob extends JobImpl { } 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; @@ -139,6 +183,7 @@ public class LoadRouterInfoJob extends JobImpl { } rif.delete(); rkf.delete(); + rkf2.delete(); } finally { if (fis1 != null) try { fis1.close(); } catch (IOException ioe) {} if (fis2 != null) try { fis2.close(); } catch (IOException ioe) {} diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java index f531971495..59f822bdaa 100644 --- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java @@ -8,14 +8,18 @@ package net.i2p.router.startup; * */ +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.InputStream; import java.io.IOException; import java.util.Properties; +import net.i2p.crypto.SigType; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.router.RouterIdentity; @@ -44,7 +48,7 @@ import net.i2p.util.SecureFileOutputStream; * router.info.rebuild file is deleted * */ -public class RebuildRouterInfoJob extends JobImpl { +class RebuildRouterInfoJob extends JobImpl { private final Log _log; private final static long REBUILD_DELAY = 45*1000; // every 30 seconds @@ -57,11 +61,11 @@ public class RebuildRouterInfoJob extends JobImpl { public String getName() { return "Rebuild Router Info"; } public void runJob() { + throw new UnsupportedOperationException(); +/**** _log.debug("Testing to rebuild router info"); - String infoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); - File info = new File(getContext().getRouterDir(), infoFile); - String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); - File keyFile = new File(getContext().getRouterDir(), keyFilename); + File info = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME); + File keyFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME); if (!info.exists() || !keyFile.exists()) { _log.info("Router info file [" + info.getAbsolutePath() + "] or private key file [" + keyFile.getAbsolutePath() + "] deleted, rebuilding"); @@ -71,41 +75,72 @@ public class RebuildRouterInfoJob extends JobImpl { } getTiming().setStartAfter(getContext().clock().now() + REBUILD_DELAY); getContext().jobQueue().addJob(this); +****/ } void rebuildRouterInfo() { rebuildRouterInfo(true); } + /** + * @param alreadyRunning unused + */ void rebuildRouterInfo(boolean alreadyRunning) { _log.debug("Rebuilding the new router info"); RouterInfo info = null; - String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); - File infoFile = new File(getContext().getRouterDir(), infoFilename); - String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); - File keyFile = new File(getContext().getRouterDir(), keyFilename); + File infoFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME); + File keyFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS_FILENAME); + File keyFile2 = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME); - if (keyFile.exists()) { + if (keyFile2.exists() || keyFile.exists()) { // ok, no need to rebuild a brand new identity, just update what we can RouterInfo oldinfo = getContext().router().getRouterInfo(); if (oldinfo == null) { info = new RouterInfo(); - FileInputStream fis = null; + InputStream fis = null; try { - fis = new FileInputStream(keyFile); + SigType stype; + if (keyFile2.exists()) { + fis = new BufferedInputStream(new FileInputStream(keyFile2)); + byte[] magic = new byte[CreateRouterInfoJob.KEYS2_MAGIC.length]; + DataHelper.read(fis, magic); + if (!DataHelper.eq(magic, CreateRouterInfoJob.KEYS2_MAGIC)) + throw new IOException("Bad magic"); + int ctype = (int) DataHelper.readLong(fis, 2); + if (ctype != 0) + throw new IOException("Unsupported RI crypto type " + ctype); + int sstype = (int) DataHelper.readLong(fis, 2); + stype = SigType.getByCode(sstype); + if (stype == null || !stype.isAvailable()) + throw new IOException("Unsupported RI sig type " + stype); + DataHelper.skip(fis, CreateRouterInfoJob.KEYS2_UNUSED_BYTES); + } else { + fis = new BufferedInputStream(new FileInputStream(keyFile)); + stype = SigType.DSA_SHA1; + } PrivateKey privkey = new PrivateKey(); privkey.readBytes(fis); - SigningPrivateKey signingPrivKey = new SigningPrivateKey(); + SigningPrivateKey signingPrivKey = new SigningPrivateKey(stype); signingPrivKey.readBytes(fis); PublicKey pubkey = new PublicKey(); pubkey.readBytes(fis); - SigningPublicKey signingPubKey = new SigningPublicKey(); + SigningPublicKey signingPubKey = new SigningPublicKey(stype); + byte[] padding; + int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length(); + if (padLen > 0) { + padding = new byte[padLen]; + DataHelper.read(fis, padding); + } else { + padding = null; + } signingPubKey.readBytes(fis); RouterIdentity ident = new RouterIdentity(); - Certificate cert = getContext().router().createCertificate(); + Certificate cert = CreateRouterInfoJob.createCertificate(getContext(), signingPubKey); ident.setCertificate(cert); ident.setPublicKey(pubkey); ident.setSigningPublicKey(signingPubKey); + if (padding != null) + ident.setPadding(padding); info.setIdentity(ident); } catch (Exception e) { _log.log(Log.CRIT, "Error reading in the key data from " + keyFile.getAbsolutePath(), e); @@ -160,12 +195,14 @@ public class RebuildRouterInfoJob extends JobImpl { _log.warn("Private key file " + keyFile.getAbsolutePath() + " deleted! Rebuilding a brand new router identity!"); // this proc writes the keys and info to the file as well as builds the latest and greatest info CreateRouterInfoJob j = new CreateRouterInfoJob(getContext(), null); - info = j.createRouterInfo(); + synchronized (getContext().router().routerInfoFileLock) { + info = j.createRouterInfo(); + } } //MessageHistory.initialize(); getContext().router().setRouterInfo(info); - _log.info("Router info rebuilt and stored at " + infoFilename + " [" + info + "]"); + _log.info("Router info rebuilt and stored at " + infoFile + " [" + info + "]"); } } diff --git a/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java b/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java index 50d840d432..672bf702aa 100644 --- a/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java +++ b/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java @@ -12,7 +12,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; /** start I2CP interface */ -public class StartAcceptingClientsJob extends JobImpl { +class StartAcceptingClientsJob extends JobImpl { public StartAcceptingClientsJob(RouterContext context) { super(context); diff --git a/router/java/src/net/i2p/router/startup/WorkingDir.java b/router/java/src/net/i2p/router/startup/WorkingDir.java index 3763f2e7a7..15a3e7fb44 100644 --- a/router/java/src/net/i2p/router/startup/WorkingDir.java +++ b/router/java/src/net/i2p/router/startup/WorkingDir.java @@ -147,7 +147,7 @@ public class WorkingDir { // Check for a router.keys file or logs dir, if either exists it's an old install, // and only migrate the data files if told to do so // (router.keys could be deleted later by a killkeys()) - test = new File(oldDirf, "router.keys"); + test = new File(oldDirf, CreateRouterInfoJob.KEYS_FILENAME); boolean oldInstall = test.exists(); if (!oldInstall) { test = new File(oldDirf, "logs"); diff --git a/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java b/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java index 7a8868985c..6a14a51346 100644 --- a/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java +++ b/router/java/src/net/i2p/router/tasks/PersistRouterInfoJob.java @@ -15,8 +15,8 @@ import java.io.IOException; import net.i2p.data.DataFormatException; import net.i2p.data.router.RouterInfo; import net.i2p.router.JobImpl; -import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.startup.CreateRouterInfoJob; import net.i2p.util.Log; import net.i2p.util.SecureFileOutputStream; @@ -37,8 +37,7 @@ public class PersistRouterInfoJob extends JobImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("Persisting updated router info"); - String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); - File infoFile = new File(getContext().getRouterDir(), infoFilename); + File infoFile = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME); RouterInfo info = getContext().router().getRouterInfo();