/** what IP Alice is reachable on */
 public void readIP(byte target[], int targetOffset) {
   int offset = readBodyOffset() + 4;
   int size = _message[offset] & 0xff;
   offset++;
   System.arraycopy(_message, offset, target, targetOffset, size);
   if (_log.shouldLog(Log.DEBUG))
     _log.debug("read alice ip: " + Base64.encode(target, targetOffset, size));
 }
 /** unused */
 public void readChallengeSize(byte target[], int targetOffset) {
   int offset = readBodyOffset() + 4;
   offset += _message[offset] & 0xff;
   offset += 1 + 2;
   int sz = _message[offset] & 0xff;
   offset++;
   System.arraycopy(_message, offset, target, targetOffset, sz);
   if (_log.shouldLog(Log.DEBUG)) _log.debug("read challenge data: " + Base64.encode(target));
 }
 public void readAliceIntroKey(byte target[], int targetOffset) {
   int offset = readBodyOffset() + 4;
   offset += _message[offset] & 0xff;
   offset += 1 + 2;
   int sz = _message[offset] & 0xff;
   offset++;
   offset += sz;
   System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES);
   if (_log.shouldLog(Log.DEBUG))
     _log.debug(
         "read alice intro key: "
             + Base64.encode(target, targetOffset, SessionKey.KEYSIZE_BYTES)
             + " packet size: "
             + _payloadLength
             + " off: "
             + offset
             + " data: "
             + Base64.encode(_message));
 }
    public void toRawString(StringBuilder buf) throws DataFormatException {
      UDPPacketReader.this.toRawString(buf);
      buf.append(" payload: ");

      int off = getFragmentBegin(0); // first fragment
      off += 4 + 1; // messageId + fragment info
      int size = ((int) DataHelper.fromLong(_message, off, 2)) & 0x3FFF;
      off += 2;
      buf.append(Base64.encode(_message, off, size));
    }
  public synchronized void receiveSessionCreated(UDPPacketReader.SessionCreatedReader reader) {
    if (_currentState == OutboundState.OB_STATE_VALIDATION_FAILED) {
      if (_log.shouldLog(Log.WARN)) _log.warn("Session created already failed");
      return;
    }
    if (_receivedY != null) {
      if (_log.shouldLog(Log.DEBUG)) _log.debug("Session created already received, ignoring");
      return; // already received
    }
    _receivedY = new byte[UDPPacketReader.SessionCreatedReader.Y_LENGTH];
    reader.readY(_receivedY, 0);
    if (_aliceIP == null) _aliceIP = new byte[reader.readIPSize()];
    reader.readIP(_aliceIP, 0);
    _alicePort = reader.readPort();
    _receivedRelayTag = reader.readRelayTag();
    _receivedSignedOnTime = reader.readSignedOnTime();
    _receivedEncryptedSignature = new byte[Signature.SIGNATURE_BYTES + 8];
    reader.readEncryptedSignature(_receivedEncryptedSignature, 0);
    _receivedIV = new byte[UDPPacket.IV_SIZE];
    reader.readIV(_receivedIV, 0);

    if (_log.shouldLog(Log.DEBUG))
      _log.debug(
          "Receive session created:Sig: "
              + Base64.encode(_receivedEncryptedSignature)
              + "receivedIV: "
              + Base64.encode(_receivedIV)
              + "AliceIP: "
              + Addresses.toString(_aliceIP)
              + " RelayTag: "
              + _receivedRelayTag
              + " SignedOn: "
              + _receivedSignedOnTime
              + ' '
              + this.toString());

    if (_currentState == OutboundState.OB_STATE_UNKNOWN
        || _currentState == OutboundState.OB_STATE_REQUEST_SENT
        || _currentState == OutboundState.OB_STATE_INTRODUCED
        || _currentState == OutboundState.OB_STATE_PENDING_INTRO)
      _currentState = OutboundState.OB_STATE_CREATED_RECEIVED;
    packetReceived();
  }
 public long readSignedOnTime() {
   int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2 + 4;
   long rv = DataHelper.fromLong(_message, offset, 4);
   if (_log.shouldLog(Log.DEBUG))
     _log.debug(
         "Signed on time offset: "
             + offset
             + " val: "
             + rv
             + "\nRawCreated: "
             + Base64.encode(_message, _payloadBeginOffset, _payloadLength));
   return rv;
 }
 private Hash getHash(String name) {
   String key = name.substring(PREFIX.length());
   key = key.substring(0, 44);
   // Hash h = new Hash();
   try {
     // h.fromBase64(key);
     byte[] b = Base64.decode(key);
     if (b == null) return null;
     Hash h = Hash.create(b);
     return h;
   } catch (Exception dfe) {
     _log.warn("Invalid base64 [" + key + "]", dfe);
     return null;
   }
 }
Exemple #8
0
  /* Parse and execute a DEST message*/
  protected boolean execDestMessage(String opcode, Properties props) {

    if (opcode.equals("GENERATE")) {
      if (!props.isEmpty()) {
        _log.debug("Properties specified in DEST GENERATE message");
        return false;
      }

      ByteArrayOutputStream priv = new ByteArrayOutputStream();
      ByteArrayOutputStream pub = new ByteArrayOutputStream();

      SAMUtils.genRandomKey(priv, pub);
      return writeString(
          "DEST REPLY"
              + " PUB="
              + Base64.encode(pub.toByteArray())
              + " PRIV="
              + Base64.encode(priv.toByteArray())
              + "\n");
    } else {
      _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\"");
      return false;
    }
  }
  /* Parse and execute a DEST message*/
  protected boolean execDestMessage(String opcode, Properties props) {

    if (opcode.equals("GENERATE")) {
      String sigTypeStr = props.getProperty("SIGNATURE_TYPE");
      SigType sigType;
      if (sigTypeStr != null) {
        sigType = SigType.parseSigType(sigTypeStr);
        if (sigType == null) {
          writeString(
              "DEST REPLY RESULT=I2P_ERROR MESSAGE=\"SIGNATURE_TYPE "
                  + sigTypeStr
                  + " unsupported\"\n");
          return false;
        }
      } else {
        sigType = SigType.DSA_SHA1;
      }

      ByteArrayOutputStream priv = new ByteArrayOutputStream(663);
      ByteArrayOutputStream pub = new ByteArrayOutputStream(387);

      SAMUtils.genRandomKey(priv, pub, sigType);
      return writeString(
          "DEST REPLY"
              + " PUB="
              + Base64.encode(pub.toByteArray())
              + " PRIV="
              + Base64.encode(priv.toByteArray())
              + "\n");
    } else {
      writeString("DEST REPLY RESULT=I2P_ERROR MESSAGE=\"DEST GENERATE required\"");
      if (_log.shouldLog(Log.DEBUG))
        _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\"");
      return false;
    }
  }
  /**
   * Verify: Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's new relay tag + Bob's
   * signed on time Caller must synch on this.
   */
  private boolean verifySessionCreated() {
    byte signed[] =
        new byte
            [256
                + 256 // X + Y
                + _aliceIP.length
                + 2
                + _bobIP.length
                + 2
                + 4 // sent relay tag
                + 4 // signed on time
            ];

    int off = 0;
    System.arraycopy(_sentX, 0, signed, off, _sentX.length);
    off += _sentX.length;
    System.arraycopy(_receivedY, 0, signed, off, _receivedY.length);
    off += _receivedY.length;
    System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length);
    off += _aliceIP.length;
    DataHelper.toLong(signed, off, 2, _alicePort);
    off += 2;
    System.arraycopy(_bobIP, 0, signed, off, _bobIP.length);
    off += _bobIP.length;
    DataHelper.toLong(signed, off, 2, _bobPort);
    off += 2;
    DataHelper.toLong(signed, off, 4, _receivedRelayTag);
    off += 4;
    DataHelper.toLong(signed, off, 4, _receivedSignedOnTime);
    boolean valid =
        _context
            .dsa()
            .verifySignature(_receivedSignature, signed, _remotePeer.getSigningPublicKey());
    if (_log.shouldLog(Log.DEBUG) || (_log.shouldLog(Log.WARN) && !valid)) {
      StringBuilder buf = new StringBuilder(128);
      buf.append("Signed sessionCreated:");
      buf.append(" Alice: ").append(Addresses.toString(_aliceIP, _alicePort));
      buf.append(" Bob: ").append(Addresses.toString(_bobIP, _bobPort));
      buf.append(" RelayTag: ").append(_receivedRelayTag);
      buf.append(" SignedOn: ").append(_receivedSignedOnTime);
      buf.append(" signature: ").append(Base64.encode(_receivedSignature.getData()));
      if (valid) _log.debug(buf.toString());
      else if (_log.shouldLog(Log.WARN)) _log.warn("INVALID: " + buf.toString());
    }
    return valid;
  }
 /**
  * decrypt the signature (and subsequent pad bytes) with the additional layer of encryption using
  * the negotiated key along side the packet's IV Caller must synch on this.
  */
 private void decryptSignature() {
   if (_receivedEncryptedSignature == null)
     throw new NullPointerException("encrypted signature is null! this=" + this.toString());
   else if (_sessionKey == null) throw new NullPointerException("SessionKey is null!");
   else if (_receivedIV == null) throw new NullPointerException("IV is null!");
   _context
       .aes()
       .decrypt(
           _receivedEncryptedSignature,
           0,
           _receivedEncryptedSignature,
           0,
           _sessionKey,
           _receivedIV,
           _receivedEncryptedSignature.length);
   byte signatureBytes[] = new byte[Signature.SIGNATURE_BYTES];
   System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, Signature.SIGNATURE_BYTES);
   _receivedSignature = new Signature(signatureBytes);
   if (_log.shouldLog(Log.DEBUG))
     _log.debug("Decrypted received signature: " + Base64.encode(signatureBytes));
 }
Exemple #12
0
 /** Verify with the "olddest" property's public key using the "oldsig" property */
 public boolean hasValidInnerSig() {
   if (props == null || name == null || dest == null) return false;
   boolean rv = false;
   // don't cache result
   if (true) {
     StringWriter buf = new StringWriter(1024);
     String sig = props.getProperty(PROP_OLDSIG);
     String olddest = props.getProperty(PROP_OLDDEST);
     if (sig == null || olddest == null) return false;
     buf.append(name);
     buf.append(KV_SEPARATOR);
     buf.append(dest);
     try {
       writeProps(buf, true, true);
     } catch (IOException ioe) {
       // won't happen
       return false;
     }
     byte[] sdata = Base64.decode(sig);
     if (sdata == null) return false;
     Destination d;
     try {
       d = new Destination(olddest);
     } catch (DataFormatException dfe) {
       return false;
     }
     SigningPublicKey spk = d.getSigningPublicKey();
     SigType type = spk.getType();
     if (type == null) return false;
     Signature s;
     try {
       s = new Signature(type, sdata);
     } catch (IllegalArgumentException iae) {
       return false;
     }
     rv = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk);
   }
   return rv;
 }
Exemple #13
0
  /* Parse and execute a SESSION message */
  protected boolean execSessionMessage(String opcode, Properties props) {

    String dest = "BUG!";

    try {
      if (opcode.equals("CREATE")) {
        if ((getRawSession() != null)
            || (getDatagramSession() != null)
            || (getStreamSession() != null)) {
          if (_log.shouldLog(Log.DEBUG))
            _log.debug("Trying to create a session, but one still exists");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
        }
        if (props.isEmpty()) {
          if (_log.shouldLog(Log.DEBUG))
            _log.debug("No parameters specified in SESSION CREATE message");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
        }

        dest = props.getProperty("DESTINATION");
        if (dest == null) {
          if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION DESTINATION parameter not specified");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
        }
        props.remove("DESTINATION");

        String destKeystream = null;

        if (dest.equals("TRANSIENT")) {
          _log.debug("TRANSIENT destination requested");
          ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
          SAMUtils.genRandomKey(priv, null);

          destKeystream = Base64.encode(priv.toByteArray());
        } else {
          destKeystream = bridge.getKeystream(dest);
          if (destKeystream == null) {
            if (_log.shouldLog(Log.DEBUG))
              _log.debug(
                  "Custom destination specified ["
                      + dest
                      + "] but it isn't known, creating a new one");
            ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
            SAMUtils.genRandomKey(baos, null);
            destKeystream = Base64.encode(baos.toByteArray());
            bridge.addKeystream(dest, destKeystream);
          } else {
            if (_log.shouldLog(Log.DEBUG))
              _log.debug("Custom destination specified [" + dest + "] and it is already known");
          }
        }

        String style = props.getProperty("STYLE");
        if (style == null) {
          if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION STYLE parameter not specified");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
        }
        props.remove("STYLE");

        // Unconditionally override what the client may have set
        // (iMule sets BestEffort) as None is more efficient
        // and the client has no way to access delivery notifications
        props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE);

        if (style.equals("RAW")) {
          rawSession = new SAMRawSession(destKeystream, props, this);
        } else if (style.equals("DATAGRAM")) {
          datagramSession = new SAMDatagramSession(destKeystream, props, this);
        } else if (style.equals("STREAM")) {
          String dir = props.getProperty("DIRECTION");
          if (dir == null) {
            if (_log.shouldLog(Log.DEBUG))
              _log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH");
            dir = "BOTH";
          } else if (!dir.equals("CREATE") && !dir.equals("RECEIVE") && !dir.equals("BOTH")) {
            if (_log.shouldLog(Log.DEBUG))
              _log.debug("Unknown DIRECTION parameter value: [" + dir + "]");
            return writeString(
                "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
          } else {
            props.remove("DIRECTION");
          }

          streamSession = newSAMStreamSession(destKeystream, dir, props);
        } else {
          if (_log.shouldLog(Log.DEBUG))
            _log.debug("Unrecognized SESSION STYLE: \"" + style + "\"");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
        }
        return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n");
      } else {
        if (_log.shouldLog(Log.DEBUG))
          _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\"");
        return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
      }
    } catch (DataFormatException e) {
      if (_log.shouldLog(Log.DEBUG)) _log.debug("Invalid destination specified");
      return writeString(
          "SESSION STATUS RESULT=INVALID_KEY DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (I2PSessionException e) {
      if (_log.shouldLog(Log.DEBUG)) _log.debug("I2P error when instantiating session", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (SAMException e) {
      _log.error("Unexpected SAM error", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (IOException e) {
      _log.error("Unexpected IOException", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    }
  }
 public void toRawString(StringBuilder buf) {
   if (_message != null) buf.append(Base64.encode(_message, _payloadBeginOffset, _payloadLength));
 }
  /* Parse and execute a SESSION message */
  @Override
  protected boolean execSessionMessage(String opcode, Properties props) {

    String dest = "BUG!";
    String nick = null;
    boolean ok = false;

    try {
      if (opcode.equals("CREATE")) {
        if ((this.getRawSession() != null)
            || (this.getDatagramSession() != null)
            || (this.getStreamSession() != null)) {
          _log.debug("Trying to create a session, but one still exists");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
        }
        if (props == null) {
          _log.debug("No parameters specified in SESSION CREATE message");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
        }

        dest = props.getProperty("DESTINATION");
        if (dest == null) {
          _log.debug("SESSION DESTINATION parameter not specified");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
        }
        props.remove("DESTINATION");

        if (dest.equals("TRANSIENT")) {
          _log.debug("TRANSIENT destination requested");
          ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
          SAMUtils.genRandomKey(priv, null);

          dest = Base64.encode(priv.toByteArray());
        } else {
          _log.debug("Custom destination specified [" + dest + "]");
        }

        try {
          SAMUtils.checkPrivateDestination(dest);
        } catch (SAMUtils.InvalidDestination e) {
          return writeString("SESSION STATUS RESULT=INVALID_KEY\n");
        }

        nick = props.getProperty("ID");
        if (nick == null) {
          _log.debug("SESSION ID parameter not specified");
          return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n");
        }
        props.remove("ID");

        String style = props.getProperty("STYLE");
        if (style == null) {
          _log.debug("SESSION STYLE parameter not specified");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
        }
        props.remove("STYLE");

        // Unconditionally override what the client may have set
        // (iMule sets BestEffort) as None is more efficient
        // and the client has no way to access delivery notifications
        i2cpProps.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE);

        // Record the session in the database sSessionsHash
        Properties allProps = new Properties();
        allProps.putAll(i2cpProps);
        allProps.putAll(props);

        try {
          sSessionsHash.put(nick, new SessionRecord(dest, allProps, this));
        } catch (SessionsDB.ExistingId e) {
          _log.debug("SESSION ID parameter already in use");
          return writeString("SESSION STATUS RESULT=DUPLICATED_ID\n");
        } catch (SessionsDB.ExistingDest e) {
          return writeString("SESSION STATUS RESULT=DUPLICATED_DEST\n");
        }

        // Create the session

        if (style.equals("RAW")) {
          DatagramServer.getInstance(i2cpProps);
          rawSession = newSAMRawSession(nick);
          this.session = rawSession;
        } else if (style.equals("DATAGRAM")) {
          DatagramServer.getInstance(i2cpProps);
          datagramSession = newSAMDatagramSession(nick);
          this.session = datagramSession;
        } else if (style.equals("STREAM")) {
          streamSession = newSAMStreamSession(nick);
          this.session = streamSession;
        } else {
          _log.debug("Unrecognized SESSION STYLE: \"" + style + "\"");
          return writeString(
              "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
        }
        ok = true;
        return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n");
      } else {
        _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\"");
        return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
      }
    } catch (DataFormatException e) {
      _log.debug("Invalid destination specified");
      return writeString(
          "SESSION STATUS RESULT=INVALID_KEY DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (I2PSessionException e) {
      _log.debug("I2P error when instantiating session", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (SAMException e) {
      _log.info("Funny SAM error", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } catch (IOException e) {
      _log.error("Unexpected IOException", e);
      return writeString(
          "SESSION STATUS RESULT=I2P_ERROR DESTINATION="
              + dest
              + " MESSAGE=\""
              + e.getMessage()
              + "\"\n");
    } finally {
      // unregister the session if it has not been created
      if (!ok && nick != null) {
        sSessionsHash.del(nick);
        session = null;
      }
    }
  }
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(512);
      long msAgo = _context.clock().now() - readTimestamp() * 1000;
      buf.append("Data packet sent ").append(msAgo).append("ms ago ");
      buf.append("IV ");
      buf.append(
          Base64.encode(_message, _payloadBeginOffset - UDPPacket.IV_SIZE, UDPPacket.IV_SIZE));
      buf.append(" ");
      int off = readBodyOffset() + 1;
      if (readACKsIncluded()) {
        int numACKs = _message[off] & 0xff;
        off++;
        buf.append("with ACKs for ");
        for (int i = 0; i < numACKs; i++) {
          buf.append(DataHelper.fromLong(_message, off, 4)).append(' ');
          off += 4;
        }
      }
      if (readACKBitfieldsIncluded()) {
        int numBitfields = _message[off] & 0xff;
        off++;
        buf.append("with partial ACKs for ");

        try {
          for (int i = 0; i < numBitfields; i++) {
            PacketACKBitfield bf = new PacketACKBitfield(off);
            buf.append(bf.getMessageId()).append(' ');
            off += bf.getByteLength();
          }
        } catch (DataFormatException dfe) {
          buf.append("CORRUPT");
          return buf.toString();
        }
      }
      if (readExtendedDataIncluded()) {
        int size = _message[off] & 0xff;
        off++;
        buf.append("with extended size of ");
        buf.append(size);
        buf.append(' ');
        off += size;
      }

      int numFragments = _message[off] & 0xff;
      off++;
      buf.append("with fragmentCount of ");
      buf.append(numFragments);
      buf.append(' ');

      for (int i = 0; i < numFragments; i++) {
        buf.append("containing messageId ");
        buf.append(DataHelper.fromLong(_message, off, 4));
        off += 4;
        int fragNum = (_message[off] & 0xFF) >>> 1;
        boolean isLast = (_message[off] & 1) != 0;
        off++;
        buf.append(" frag# ").append(fragNum);
        buf.append(" isLast? ").append(isLast);
        buf.append(" info ").append(_message[off - 1]);
        int size = ((int) DataHelper.fromLong(_message, off, 2)) & 0x3FFF;
        off += 2;
        buf.append(" with ").append(size).append(" bytes; ");
        off += size;
      }

      return buf.toString();
    }
Exemple #17
0
  private void runTest() {
    I2PAppContext ctx = I2PAppContext.getGlobalContext();
    Log log = ctx.logManager().getLog(getClass());

    List order = pickOrder(ctx);

    TunnelCreatorConfig cfg = createConfig(ctx);
    _replyRouter = new Hash();
    byte h[] = new byte[Hash.HASH_LENGTH];
    Arrays.fill(h, (byte) 0xFF);
    _replyRouter.setData(h);
    _replyTunnel = 42;

    // populate and encrypt the message
    BuildMessageGenerator gen = new BuildMessageGenerator();
    TunnelBuildMessage msg = new TunnelBuildMessage(ctx);
    for (int i = 0; i < BuildMessageGenerator.ORDER.length; i++) {
      int hop = ((Integer) order.get(i)).intValue();
      PublicKey key = null;
      if (hop < _pubKeys.length) key = _pubKeys[hop];
      gen.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key);
    }
    gen.layeredEncrypt(ctx, msg, cfg, order);

    log.debug(
        "\n================================================================"
            + "\nMessage fully encrypted"
            + "\n================================================================");

    // now msg is fully encrypted, so lets go through the hops, decrypting and replying
    // as necessary

    BuildMessageProcessor proc = new BuildMessageProcessor(ctx);
    for (int i = 0; i < cfg.getLength(); i++) {
      // this not only decrypts the current hop's record, but encrypts the other records
      // with the reply key
      BuildRequestRecord req = proc.decrypt(ctx, msg, _peers[i], _privKeys[i]);
      if (req == null) {
        // no records matched the _peers[i], or the decryption failed
        throw new RuntimeException("foo @ " + i);
      }
      long ourId = req.readReceiveTunnelId();
      byte replyIV[] = req.readReplyIV();
      long nextId = req.readNextTunnelId();
      Hash nextPeer = req.readNextIdentity();
      boolean isInGW = req.readIsInboundGateway();
      boolean isOutEnd = req.readIsOutboundEndpoint();
      long time = req.readRequestTime();
      long now = (ctx.clock().now() / (60l * 60l * 1000l)) * (60 * 60 * 1000);
      int ourSlot = -1;

      BuildResponseRecord resp = new BuildResponseRecord();
      byte reply[] = resp.create(ctx, 0, req.readReplyKey(), req.readReplyIV(), -1);
      for (int j = 0; j < TunnelBuildMessage.RECORD_COUNT; j++) {
        if (msg.getRecord(j) == null) {
          ourSlot = j;
          msg.setRecord(j, new ByteArray(reply));
          break;
        }
      }

      log.debug(
          "Read slot "
              + ourSlot
              + " containing hop "
              + i
              + " @ "
              + _peers[i].toBase64()
              + " receives on "
              + ourId
              + " w/ replyIV "
              + Base64.encode(replyIV)
              + " sending to "
              + nextId
              + " on "
              + nextPeer.toBase64()
              + " inGW? "
              + isInGW
              + " outEnd? "
              + isOutEnd
              + " time difference "
              + (now - time));
    }

    log.debug(
        "\n================================================================"
            + "\nAll hops traversed and replies gathered"
            + "\n================================================================");

    // now all of the replies are populated, toss 'em into a reply message and handle it
    TunnelBuildReplyMessage reply = new TunnelBuildReplyMessage(ctx);
    for (int i = 0; i < TunnelBuildMessage.RECORD_COUNT; i++) reply.setRecord(i, msg.getRecord(i));

    BuildReplyHandler handler = new BuildReplyHandler();
    int statuses[] = handler.decrypt(ctx, reply, cfg, order);
    if (statuses == null) throw new RuntimeException("bar");
    boolean allAgree = true;
    for (int i = 0; i < cfg.getLength(); i++) {
      Hash peer = cfg.getPeer(i);
      int record = ((Integer) order.get(i)).intValue();
      if (statuses[record] != 0) allAgree = false;
      // else
      //    penalize peer according to the rejection cause
    }

    log.debug(
        "\n================================================================"
            + "\nAll peers agree? "
            + allAgree
            + "\n================================================================");
  }