public String getProof() { StringBuilder buf = new StringBuilder(512); RouterInfo us = _context.router().getRouterInfo(); buf.append("Hash: ").append(us.getIdentity().calculateHash().toBase64()).append('\n'); // buf.append("Ident: ").append(us.getIdentity().toBase64()).append('\n'); for (RouterAddress addr : us.getAddresses()) { buf.append(addr.getTransportStyle()).append(": ").append(addr.getHost()).append('\n'); } buf.append("Caps: ").append(us.getCapabilities()).append('\n'); buf.append("Date: ").append(new Date()); // no trailing newline String msg = buf.toString(); byte[] data = DataHelper.getUTF8(msg); Signature sig = _context.dsa().sign(data, _context.keyManager().getSigningPrivateKey()); buf.setLength(0); buf.append("---BEGIN I2P SIGNED MESSAGE---\n"); buf.append(msg); buf.append("\n---BEGIN I2P SIGNATURE---\n"); buf.append(sig.toBase64()); buf.append("\n---END I2P SIGNATURE---"); return buf.toString(); }
/** * If the tunnel is short enough, and everybody in the tunnel, and the OBEP or IBGW for the paired * tunnel, all support the new variable-sized tunnel build message, then use that, otherwise the * old 8-entry version. * * @return null on error */ private static TunnelBuildMessage createTunnelBuildMessage( RouterContext ctx, TunnelPool pool, PooledTunnelCreatorConfig cfg, TunnelInfo pairedTunnel, BuildExecutor exec) { Log log = ctx.logManager().getLog(BuildRequestor.class); long replyTunnel = 0; Hash replyRouter = null; boolean useVariable = SEND_VARIABLE && cfg.getLength() <= MEDIUM_RECORDS; if (cfg.isInbound()) { // replyTunnel = 0; // as above replyRouter = ctx.routerHash(); if (useVariable) { // check the reply OBEP and all the tunnel peers except ourselves if (!supportsVariable(ctx, pairedTunnel.getPeer(pairedTunnel.getLength() - 1))) { useVariable = false; } else { for (int i = 0; i < cfg.getLength() - 1; i++) { if (!supportsVariable(ctx, cfg.getPeer(i))) { useVariable = false; break; } } } } } else { replyTunnel = pairedTunnel.getReceiveTunnelId(0).getTunnelId(); replyRouter = pairedTunnel.getPeer(0); if (useVariable) { // check the reply IBGW and all the tunnel peers except ourselves if (!supportsVariable(ctx, replyRouter)) { useVariable = false; } else { for (int i = 1; i < cfg.getLength() - 1; i++) { if (!supportsVariable(ctx, cfg.getPeer(i))) { useVariable = false; break; } } } } } // populate and encrypt the message TunnelBuildMessage msg; List<Integer> order; if (useVariable) { if (cfg.getLength() <= SHORT_RECORDS) { msg = new VariableTunnelBuildMessage(ctx, SHORT_RECORDS); order = new ArrayList<Integer>(SHORT_ORDER); } else { msg = new VariableTunnelBuildMessage(ctx, MEDIUM_RECORDS); order = new ArrayList<Integer>(MEDIUM_ORDER); } } else { msg = new TunnelBuildMessage(ctx); order = new ArrayList<Integer>(ORDER); } // This is in BuildExecutor.buildTunnel() now // long replyMessageId = ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE); // cfg.setReplyMessageId(replyMessageId); Collections.shuffle(order, ctx.random()); // randomized placement within the message cfg.setReplyOrder(order); if (log.shouldLog(Log.DEBUG)) log.debug("Build order: " + order + " for " + cfg); for (int i = 0; i < msg.getRecordCount(); i++) { int hop = order.get(i).intValue(); PublicKey key = null; if (BuildMessageGenerator.isBlank(cfg, hop)) { // erm, blank } else { Hash peer = cfg.getPeer(hop); RouterInfo peerInfo = ctx.netDb().lookupRouterInfoLocally(peer); if (peerInfo == null) { if (log.shouldLog(Log.WARN)) log.warn( "Peer selected for hop " + i + "/" + hop + " was not found locally: " + peer + " for " + cfg); return null; } else { key = peerInfo.getIdentity().getPublicKey(); } } if (log.shouldLog(Log.DEBUG)) log.debug(cfg.getReplyMessageId() + ": record " + i + "/" + hop + " has key " + key); BuildMessageGenerator.createRecord(i, hop, msg, cfg, replyRouter, replyTunnel, ctx, key); } BuildMessageGenerator.layeredEncrypt(ctx, msg, cfg, order); return msg; }
/** @since 0.7.12 */ private static boolean supportsVariable(RouterContext ctx, Hash h) { RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(h); if (ri == null) return false; String v = ri.getVersion(); return VersionComparator.comp(v, MIN_VARIABLE_VERSION) >= 0; }
private boolean shouldBeFloodfill() { if (!SigType.ECDSA_SHA256_P256.isAvailable()) return false; // Hidden trumps netDb.floodfillParticipant=true if (getContext().router().isHidden()) return false; String enabled = getContext().getProperty(PROP_FLOODFILL_PARTICIPANT, "auto"); if ("true".equals(enabled)) return true; if ("false".equals(enabled)) return false; // auto from here down // Only if not shutting down... if (getContext().router().gracefulShutdownInProgress()) return false; // ARM ElG decrypt is too slow if (SystemVersion.isARM() || SystemVersion.isAndroid()) return false; if (getContext().getBooleanProperty(UDPTransport.PROP_LAPTOP_MODE)) return false; if (getContext().commSystem().isInBadCountry()) return false; String country = getContext().commSystem().getOurCountry(); // anonymous proxy, satellite provider (not in bad country list) if ("a1".equals(country) || "a2".equals(country)) return false; // Only if up a while... if (getContext().router().getUptime() < MIN_UPTIME) return false; RouterInfo ri = getContext().router().getRouterInfo(); if (ri == null) return false; char bw = ri.getBandwidthTier().charAt(0); // Only if class M, N, O, P, X if (bw != Router.CAPABILITY_BW64 && bw != Router.CAPABILITY_BW128 && bw != Router.CAPABILITY_BW256 && bw != Router.CAPABILITY_BW512 && bw != Router.CAPABILITY_BW_UNLIMITED) return false; // This list will not include ourselves... List<Hash> floodfillPeers = _facade.getFloodfillPeers(); long now = getContext().clock().now(); // We know none at all! Must be our turn... if (floodfillPeers == null || floodfillPeers.isEmpty()) { _lastChanged = now; return true; } // Only change status every so often boolean wasFF = _facade.floodfillEnabled(); if (_lastChanged + MIN_CHANGE_DELAY > now) return wasFF; // This is similar to the qualification we do in FloodOnlySearchJob.runJob(). // Count the "good" ff peers. // // Who's not good? // the unheard-from, unprofiled, failing, unreachable and banlisted ones. // We should hear from floodfills pretty frequently so set a 60m time limit. // If unprofiled we haven't talked to them in a long time. // We aren't contacting the peer directly, so banlist doesn't strictly matter, // but it's a bad sign, and we often banlist a peer before we fail it... // // Future: use Integration calculation // int ffcount = floodfillPeers.size(); int failcount = 0; long before = now - 60 * 60 * 1000; for (Hash peer : floodfillPeers) { PeerProfile profile = getContext().profileOrganizer().getProfile(peer); if (profile == null || profile.getLastHeardFrom() < before || profile.getIsFailing() || getContext().banlist().isBanlisted(peer) || getContext().commSystem().wasUnreachable(peer)) failcount++; } if (wasFF) ffcount++; int good = ffcount - failcount; boolean happy = getContext().router().getRouterInfo().getCapabilities().indexOf('R') >= 0; // TODO - limit may still be too high // For reference, the avg lifetime job lag on my Pi is 6. // Should we consider avg. dropped ff jobs? RateStat lagStat = getContext().statManager().getRate("jobQueue.jobLag"); RateStat queueStat = getContext().statManager().getRate("router.tunnelBacklog"); happy = happy && lagStat.getRate(60 * 60 * 1000L).getAvgOrLifetimeAvg() < 25; happy = happy && queueStat.getRate(60 * 60 * 1000L).getAvgOrLifetimeAvg() < 5; // Only if we're pretty well integrated... happy = happy && _facade.getKnownRouters() >= 400; happy = happy && getContext().commSystem().countActivePeers() >= 50; happy = happy && getContext().tunnelManager().getParticipatingCount() >= 25; happy = happy && Math.abs(getContext().clock().getOffset()) < 10 * 1000; // We need an address and no introducers if (happy) { RouterAddress ra = getContext().router().getRouterInfo().getTargetAddress("SSU"); if (ra == null) happy = false; else { if (ra.getOption("ihost0") != null) happy = false; } } double elG = 0; RateStat stat = getContext().statManager().getRate("crypto.elGamal.decrypt"); if (stat != null) { Rate rate = stat.getRate(60 * 60 * 1000L); if (rate != null) { elG = rate.getAvgOrLifetimeAvg(); happy = happy && elG <= 40.0d; } } if (_log.shouldLog(Log.DEBUG)) { final RouterContext rc = getContext(); final String log = String.format( "FF criteria breakdown: happy=%b, capabilities=%s, maxLag=%d, known=%d, " + "active=%d, participating=%d, offset=%d, ssuAddr=%s ElG=%f", happy, rc.router().getRouterInfo().getCapabilities(), rc.jobQueue().getMaxLag(), _facade.getKnownRouters(), rc.commSystem().countActivePeers(), rc.tunnelManager().getParticipatingCount(), Math.abs(rc.clock().getOffset()), rc.router().getRouterInfo().getTargetAddress("SSU").toString(), elG); _log.debug(log); } // Too few, and we're reachable, let's volunteer if (good < MIN_FF && happy) { if (!wasFF) { _lastChanged = now; _log.logAlways( Log.INFO, "Only " + good + " ff peers and we want " + MIN_FF + " so we are becoming floodfill"); } return true; } // Too many, or we aren't reachable, let's stop if (good > MAX_FF || (good > MIN_FF && !happy)) { if (wasFF) { _lastChanged = now; _log.logAlways( Log.INFO, "Have " + good + " ff peers and we need only " + MIN_FF + " to " + MAX_FF + " so we are disabling floodfill; reachable? " + happy); } return false; } if (_log.shouldLog(Log.INFO)) _log.info( "Have " + good + " ff peers, not changing, enabled? " + wasFF + "; reachable? " + happy); return wasFF; }
/** * Writes 6 files: router.info (standard RI format), router.keys.dat, and 4 individual key files * under keyBackup/ * * <p>router.keys.dat file format: This is the same "eepPriv.dat" format used by the client code, * as documented in PrivateKeyFile. * * <p>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(); 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; }