Example #1
0
 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();
 }
Example #2
0
  /**
   * 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;
  }
Example #3
0
 /** @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;
 }
Example #4
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;
  }