diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java index f80878e5ea..2860037306 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java @@ -15,6 +15,7 @@ import net.i2p.client.I2PSession; import net.i2p.client.streaming.I2PSocketException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; @@ -30,6 +31,7 @@ class Connection { private final ConnectionManager _connectionManager; private final I2PSession _session; private Destination _remotePeer; + private SigningPublicKey _transientSPK; private final AtomicLong _sendStreamId = new AtomicLong(); private final AtomicLong _receiveStreamId = new AtomicLong(); private volatile long _lastSendTime; @@ -327,8 +329,9 @@ class Connection { reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); reply.setSendStreamId(_sendStreamId.get()); reply.setReceiveStreamId(_receiveStreamId.get()); - // TODO remove this someday, as of 0.9.20 we do not require it - reply.setOptionalFrom(); + // As of 0.9.20 we do not require FROM + // Removed in 0.9.39 + //reply.setOptionalFrom(); reply.setLocalPort(_localPort); reply.setRemotePort(_remotePort); // this just sends the packet - no retries or whatnot @@ -872,6 +875,35 @@ class Connection { // now that we know who the other end is, get the rtt etc. from the cache _connectionManager.updateOptsFromShare(this); } + + /** + * The key to verify signatures with. + * The transient SPK if previously received, + * else getRemotePeer().getSigningPublicKey() if previously received, + * else null. + * + * @return peer Destination or null if unset + * @since 0.9.39 + */ + public synchronized SigningPublicKey getRemoteSPK() { + if (_transientSPK != null) + return _transientSPK; + if (_remotePeer != null) + return _remotePeer.getSigningPublicKey(); + return null; + } + + /** + * @param transientSPK null ok + * @since 0.9.39 + */ + public void setRemoteTransientSPK(SigningPublicKey transientSPK) { + synchronized(this) { + if (_transientSPK != null) + throw new RuntimeException("Remote SPK already set"); + _transientSPK = transientSPK; + } + } /** * What stream do we send data to the peer on? diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java index 478dfb76ca..e0ce22c46f 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java @@ -7,7 +7,9 @@ import java.util.concurrent.TimeUnit; import net.i2p.I2PAppContext; import net.i2p.client.streaming.RouterRestartException; +import net.i2p.data.ByteArray; import net.i2p.data.Destination; +import net.i2p.util.ByteCache; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; @@ -23,6 +25,7 @@ import net.i2p.util.SimpleTimer2; class ConnectionHandler { private final I2PAppContext _context; private final Log _log; + private final ByteCache _cache = ByteCache.getInstance(32, 4*1024); private final ConnectionManager _manager; private final LinkedBlockingQueue _synQueue; private final SimpleTimer2 _timer; @@ -259,21 +262,29 @@ class ConnectionHandler { } } + /** + * Send a reset in response to this packet, but only if it + * contains a FROM field and Signature that can be verified. + * + * @param packet the incoming packet we're responding to + */ private void sendReset(Packet packet) { - boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null); + ByteArray ba = _cache.acquire(); + boolean ok = packet.verifySignature(_context, ba.getData()); + _cache.release(ba); if (!ok) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Received a spoofed SYN packet: they said they were " + packet.getOptionalFrom()); + if (_log.shouldWarn()) + _log.warn("Can't send reset in response to packet: " + packet); return; } PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom(), packet.getSession()); - reply.setFlag(Packet.FLAG_RESET); - reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); + reply.setFlag(Packet.FLAG_RESET | Packet.FLAG_SIGNATURE_INCLUDED); reply.setAckThrough(packet.getSequenceNum()); reply.setSendStreamId(packet.getReceiveStreamId()); reply.setReceiveStreamId(0); - // TODO remove this someday, as of 0.9.20 we do not require it - reply.setOptionalFrom(); + // As of 0.9.20 we do not require FROM + // Removed in 0.9.39 + //reply.setOptionalFrom(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending RST: " + reply + " because of " + packet); // this just sends the packet - no retries or whatnot diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionPacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionPacketHandler.java index 36f15899e5..a156b8475a 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionPacketHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionPacketHandler.java @@ -4,8 +4,11 @@ import java.util.List; import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.SigningPublicKey; +import net.i2p.util.ByteCache; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; @@ -23,6 +26,7 @@ import net.i2p.util.SimpleTimer; class ConnectionPacketHandler { private final I2PAppContext _context; private final Log _log; + private final ByteCache _cache = ByteCache.getInstance(32, 4*1024); public static final int MAX_SLOW_START_WINDOW = 24; @@ -514,6 +518,7 @@ class ConnectionPacketHandler { if (con.getSendStreamId() <= 0) { if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { + // this is an incoming connection. con.setSendStreamId(packet.getReceiveStreamId()); Destination dest = packet.getOptionalFrom(); if (dest == null) { @@ -522,6 +527,9 @@ class ConnectionPacketHandler { return false; } con.setRemotePeer(dest); + SigningPublicKey spk = packet.getTransientSPK(); + if (spk != null) + con.setRemoteTransientSPK(spk); return true; } else { // neither RST nor SYN and we dont have the stream id yet? @@ -535,6 +543,7 @@ class ConnectionPacketHandler { } } } else { + // getting a lot of these - why? mostly/all for acks... if (con.getSendStreamId() != packet.getReceiveStreamId()) { if (_log.shouldLog(Log.WARN)) _log.warn("Packet received with the wrong reply stream id: " @@ -553,20 +562,22 @@ class ConnectionPacketHandler { * Prior to 0.9.20, the reset packet must contain a FROM field, * and we used that for verification. * As of 0.9.20, we correctly use the connection's remote peer. + * + * @param con non-null */ private void verifyReset(Packet packet, Connection con) { if (con.getReceiveStreamId() == packet.getSendStreamId()) { - Destination from = con.getRemotePeer(); - if (from == null) - from = packet.getOptionalFrom(); - boolean ok = packet.verifySignature(_context, from, null); + SigningPublicKey spk = con.getRemoteSPK(); + ByteArray ba = _cache.acquire(); + boolean ok = packet.verifySignature(_context, spk, ba.getData()); + _cache.release(ba); if (!ok) { if (_log.shouldLog(Log.ERROR)) _log.error("Received unsigned / forged RST on " + con); return; } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Reset received"); + if (_log.shouldWarn()) + _log.warn("Reset received on " + con);; // ok, valid RST con.resetReceived(); con.eventOccurred(); @@ -587,17 +598,18 @@ class ConnectionPacketHandler { /** * Verify the signature if necessary. * + * @param con non-null * @throws I2PException if the signature was necessary and it was invalid */ private void verifySignature(Packet packet, Connection con) throws I2PException { // verify the signature if necessary if (con.getOptions().getRequireFullySigned() || - packet.isFlagSet(Packet.FLAG_SYNCHRONIZE | Packet.FLAG_CLOSE)) { + packet.isFlagSet(Packet.FLAG_SYNCHRONIZE | Packet.FLAG_CLOSE | Packet.FLAG_SIGNATURE_INCLUDED)) { // we need a valid signature - Destination from = con.getRemotePeer(); - if (from == null) - from = packet.getOptionalFrom(); - boolean sigOk = packet.verifySignature(_context, from, null); + SigningPublicKey spk = con.getRemoteSPK(); + ByteArray ba = _cache.acquire(); + boolean sigOk = packet.verifySignature(_context, spk, null); + _cache.release(ba); if (!sigOk) { throw new I2PException("Received unsigned / forged packet: " + packet); } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java index f5b2b89cf4..1e90ce951f 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java @@ -77,6 +77,10 @@ class MessageHandler implements I2PSessionMuxedListener { packet.setRemotePort(fromPort); packet.setLocalPort(toPort); _manager.getPacketHandler().receivePacket(packet); + } catch (IndexOutOfBoundsException ioobe) { + _context.statManager().addRateData("stream.packetReceiveFailure", 1); + if (_log.shouldWarn()) + _log.warn("Received an invalid packet", ioobe); } catch (IllegalArgumentException iae) { _context.statManager().addRateData("stream.packetReceiveFailure", 1); if (_log.shouldLog(Log.WARN)) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java index 72751f0c33..78612d6bf0 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java @@ -1,8 +1,10 @@ package net.i2p.client.streaming.impl; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; +import java.util.Date; import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; @@ -82,6 +84,11 @@ class Packet { protected Destination _optionFrom; private int _optionDelay; private int _optionMaxSize; + // following 3 for ofline sigs + protected long _transientExpires; + protected Signature _offlineSignature; + protected SigningPublicKey _transientSigningPublicKey; + // ports private int _localPort; private int _remotePort; @@ -161,6 +168,12 @@ class Packet { * If set, this packet doesn't really want to ack anything */ public static final int FLAG_NO_ACK = (1 << 10); + + /** + * If set, an offline signing block is in the options. + * @since 0.9.39 + */ + public static final int FLAG_SIGNATURE_OFFLINE = (1 << 11); public static final int DEFAULT_MAX_SIZE = 32*1024; protected static final int MAX_DELAY_REQUEST = 65535; @@ -348,15 +361,12 @@ class Packet { */ public Destination getOptionalFrom() { return _optionFrom; } - /** - * This sets the from field in the packet to the Destination for the session - * provided in the constructor. - * This also sets flag FLAG_FROM_INCLUDED + /** + * Only if an offline signing block was included, else null + * + * @since 0.9.39 */ - public void setOptionalFrom() { - setFlag(FLAG_FROM_INCLUDED, true); - _optionFrom = _session.getMyDestination(); - } + public SigningPublicKey getTransientSPK() { return _transientSigningPublicKey; } /** * How many milliseconds the sender of this packet wants the recipient @@ -478,6 +488,11 @@ class Packet { optionSize += _optionFrom.size(); if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) optionSize += 2; + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + optionSize += 6; + optionSize += _transientSigningPublicKey.length(); + optionSize += _offlineSignature.length(); + } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (fakeSigLen > 0) optionSize += fakeSigLen; @@ -501,6 +516,18 @@ class Packet { DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE); cur += 2; } + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + DataHelper.toLong(buffer, cur, 4, _transientExpires / 1000); + cur += 4; + DataHelper.toLong(buffer, cur, 2, _transientSigningPublicKey.getType().getCode()); + cur += 2; + int len = _transientSigningPublicKey.length(); + System.arraycopy(_transientSigningPublicKey.getData(), 0, buffer, cur, len); + cur += len; + len = _offlineSignature.length(); + System.arraycopy(_offlineSignature.getData(), 0, buffer, cur, len); + cur += len; + } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (fakeSigLen == 0) { // we're signing (or validating) @@ -535,9 +562,8 @@ class Packet { /** * how large would this packet be if we wrote it - * @return How large the current packet would be * - * @throws IllegalStateException + * @return How large the current packet would be */ private int writtenSize() { //int size = 0; @@ -564,6 +590,11 @@ class Packet { size += 2; if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) size += _optionSignature.length(); + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + size += 6; + size += _transientSigningPublicKey.length(); + size += _offlineSignature.length(); + } if (_payload != null) { size += _payload.getValid(); @@ -583,6 +614,7 @@ class Packet { * part of the packet? * * @throws IllegalArgumentException if the data is b0rked + * @throws IndexOutOfBoundsException if the data is b0rked */ public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException { if (buffer.length - offset < length) @@ -657,11 +689,39 @@ class Packet { setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2)); cur += 2; } + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + _transientExpires = DataHelper.fromLong(buffer, cur, 4) * 1000; + cur += 4; + int itype = (int) DataHelper.fromLong(buffer, cur, 2); + cur += 2; + SigType type = SigType.getByCode(itype); + if (type == null || !type.isAvailable()) + throw new IllegalArgumentException("Unsupported transient sig type: " + itype); + _transientSigningPublicKey = new SigningPublicKey(type); + byte[] buf = new byte[_transientSigningPublicKey.length()]; + System.arraycopy(buffer, cur, buf, 0, buf.length); + _transientSigningPublicKey.setData(buf); + cur += buf.length; + if (_optionFrom != null) { + type = _optionFrom.getSigningPublicKey().getType(); + } else { + throw new IllegalArgumentException("TODO offline w/o FROM"); + } + _offlineSignature = new Signature(type); + buf = new byte[_offlineSignature.length()]; + System.arraycopy(buffer, cur, buf, 0, buf.length); + _offlineSignature.setData(buf); + cur += buf.length; + } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { Signature optionSignature; - Destination from = getOptionalFrom(); - if (from != null) { - optionSignature = new Signature(from.getSigningPublicKey().getType()); + if (_optionFrom != null) { + SigType type; + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) + type = _transientSigningPublicKey.getType(); + else + type = _optionFrom.getSigningPublicKey().getType(); + optionSignature = new Signature(type); } else { // super cheat for now, look for correct type, // assume no more options. If we add to the options @@ -694,32 +754,77 @@ class Packet { cur += buf.length; } } - + + /** + * Determine whether the signature on the data is valid. + * Packet MUST have a FROM option or will return false. + * + * @param ctx Application context + * @param buffer data to validate with signature, or null to use our own buffer. + * @return true if the signature exists and validates against the data, + * false otherwise. + * @since 0.9.39 + */ + public boolean verifySignature(I2PAppContext ctx, byte buffer[]) { + return verifySignature(ctx, null, buffer); + } + /** * Determine whether the signature on the data is valid. * * @param ctx Application context - * @param from the Destination the data came from - * @param buffer data to validate with signature + * @param spk Signing key to verify with, ONLY if there is no FROM field in this packet. + * May be the SPK from a FROM field or offline sig field from a previous packet on this connection. + * Ignored if this packet contains a FROM option block. + * Null ok if none available. + * @param buffer data to validate with signature, or null to use our own buffer. * @return true if the signature exists and validates against the data, * false otherwise. */ - public boolean verifySignature(I2PAppContext ctx, Destination from, byte buffer[]) { + public boolean verifySignature(I2PAppContext ctx, SigningPublicKey altSPK, byte buffer[]) { if (!isFlagSet(FLAG_SIGNATURE_INCLUDED)) return false; if (_optionSignature == null) return false; + SigningPublicKey spk = _optionFrom != null ? _optionFrom.getSigningPublicKey() : altSPK; // prevent receiveNewSyn() ... !active ... sendReset() ... verifySignature ... NPE - if (from == null) return false; + if (spk == null) return false; int size = writtenSize(); if (buffer == null) buffer = new byte[size]; - SigningPublicKey spk = from.getSigningPublicKey(); + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + if (_transientExpires < ctx.clock().now()) { + Log l = ctx.logManager().getLog(Packet.class); + if (l.shouldLog(Log.WARN)) + l.warn("Offline signature expired " + toString()); + return false; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(6 + _transientSigningPublicKey.length()); + try { + DataHelper.writeLong(baos, 4, _transientExpires / 1000); + DataHelper.writeLong(baos, 2, _transientSigningPublicKey.getType().getCode()); + _transientSigningPublicKey.writeBytes(baos); + } catch (IOException ioe) { + return false; + } catch (DataFormatException dfe) { + return false; + } + byte[] data = baos.toByteArray(); + boolean ok = ctx.dsa().verifySignature(_offlineSignature, data, 0, data.length, spk); + if (!ok) { + Log l = ctx.logManager().getLog(Packet.class); + if (l.shouldLog(Log.WARN)) + l.warn("Offline signature failed on " + toString()); + return false; + } + // use transient key to verify + spk = _transientSigningPublicKey; + } SigType type = spk.getType(); - if (type == null) { + if (type == null || !type.isAvailable()) { Log l = ctx.logManager().getLog(Packet.class); if (l.shouldLog(Log.WARN)) - l.warn("Unknown sig type in " + from + " cannot verify " + toString()); + l.warn("Unknown sig type in " + spk + " cannot verify " + toString()); return false; } int written = writePacket(buffer, 0, type.getSigLen()); @@ -731,18 +836,27 @@ class Packet { // Fixup of signature if we guessed wrong on the type in readPacket(), which could happen // on a close or reset packet where we have a signature without a FROM if (type != _optionSignature.getType() && - type.getSigLen() == _optionSignature.length()) + type.getSigLen() == _optionSignature.length()) { + //Log l = ctx.logManager().getLog(Packet.class); + //if (l.shouldDebug()) + // l.debug("Fixing up sig type from " + _optionSignature.getType() + " to " + type); _optionSignature = new Signature(type, _optionSignature.getData()); + } - boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, spk); + boolean ok; + try { + ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, spk); + } catch (IllegalArgumentException iae) { + // sigtype mismatch + Log l = ctx.logManager().getLog(Packet.class); + if (l.shouldLog(Log.WARN)) + l.warn("Signature failed on " + toString(), iae); + ok = false; + } if (!ok) { Log l = ctx.logManager().getLog(Packet.class); if (l.shouldLog(Log.WARN)) - l.warn("Signature failed on " + toString(), new Exception("moo")); - //if (false) { - // l.error(Base64.encode(buffer, 0, size)); - // l.error("Signature: " + Base64.encode(_optionSignature.getData())); - //} + l.warn("Signature failed on " + toString() + " using SPK " + spk); } return ok; } @@ -776,6 +890,11 @@ class Packet { } private final void toFlagString(StringBuilder buf) { + if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN"); + if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE"); + if (isFlagSet(FLAG_RESET)) buf.append(" RESET"); + if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO"); + if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM ").append(_optionFrom.size()); if (isFlagSet(FLAG_NO_ACK)) buf.append(" NO_ACK"); else @@ -786,21 +905,30 @@ class Packet { buf.append(' ').append(_nacks[i]); } } - if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE"); if (isFlagSet(FLAG_DELAY_REQUESTED)) buf.append(" DELAY ").append(_optionDelay); - if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO"); - if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM ").append(_optionFrom.size()); if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS ").append(_optionMaxSize); if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE"); - if (isFlagSet(FLAG_RESET)) buf.append(" RESET"); + if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ"); + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + if (_transientExpires != 0) + buf.append(" TRANSEXP ").append(new Date(_transientExpires)); + else + buf.append(" (no expiration)"); + if (_transientSigningPublicKey != null) + buf.append(" TRANSKEY ").append(_transientSigningPublicKey.getType()); + else + buf.append(" (no key data)"); + if (_offlineSignature != null) + buf.append(" OFFSIG ").append(_offlineSignature.getType()); + else + buf.append(" (no offline sig data)"); + } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (_optionSignature != null) - buf.append(" SIG ").append(_optionSignature.length()); + buf.append(" SIG ").append(_optionSignature.getType()); else buf.append(" (to be signed)"); } - if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ"); - if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN"); } /** Generate a pcap/tcpdump-compatible format, diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketHandler.java index 9c094461b0..c1ad4cda4c 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketHandler.java @@ -5,7 +5,10 @@ import java.util.Date; import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.data.ByteArray; import net.i2p.data.Destination; +import net.i2p.data.SigningPublicKey; +import net.i2p.util.ByteCache; import net.i2p.util.Log; /** @@ -18,6 +21,7 @@ class PacketHandler { private final ConnectionManager _manager; private final I2PAppContext _context; private final Log _log; + private final ByteCache _cache = ByteCache.getInstance(32, 4*1024); //private int _lastDelay; //private int _dropped; @@ -179,8 +183,11 @@ class PacketHandler { long oldId = con.getSendStreamId(); if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { if (oldId <= 0) { - // con fully established, w00t + // outgoing con now fully established con.setSendStreamId(packet.getReceiveStreamId()); + SigningPublicKey spk = packet.getTransientSPK(); + if (spk != null) + con.setRemoteTransientSPK(spk); } else if (oldId == packet.getReceiveStreamId()) { // ok, as expected... } else { @@ -195,9 +202,14 @@ class PacketHandler { try { con.getPacketHandler().receivePacket(packet, con); } catch (I2PException ie) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Received forged packet for " + con + "/" + oldId + ": " + packet, ie); + if (_log.shouldWarn()) + _log.warn("Sig verify fail for " + con + "/" + oldId + ": " + packet, ie); con.setSendStreamId(oldId); + if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { + // send a reset, it's a known con, so it's unlikely to be spoofed + // don't bother to send reset if it's just a CLOSE + sendResetUnverified(packet); + } } } else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { if (_log.shouldLog(Log.WARN)) @@ -238,19 +250,34 @@ class PacketHandler { Destination from = packet.getOptionalFrom(); if (from == null) return; - boolean ok = packet.verifySignature(_context, from, null); + ByteArray ba = _cache.acquire(); + boolean ok = packet.verifySignature(_context, ba.getData()); + _cache.release(ba); if (!ok) { if (_log.shouldLog(Log.WARN)) - _log.warn("Can't send reset after recv spoofed packet: " + packet); + _log.warn("Can't send reset in response to packet: " + packet); return; } - PacketLocal reply = new PacketLocal(_context, from, packet.getSession()); + sendResetUnverified(packet); + } + + /** + * This sends a reset back to the place this packet came from. + * Packet MUST have a FROM option. + * This is not associated with a connection, so no con stats are updated. + * + * @param packet incoming packet to be replied to, MUST have a FROM option + * @since 0.9.39 + */ + private void sendResetUnverified(Packet packet) { + PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom(), packet.getSession()); reply.setFlag(Packet.FLAG_RESET); reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); reply.setSendStreamId(packet.getReceiveStreamId()); reply.setReceiveStreamId(packet.getSendStreamId()); - // TODO remove this someday, as of 0.9.20 we do not require it - reply.setOptionalFrom(); + // As of 0.9.20 we do not require FROM + // Removed in 0.9.39 + //reply.setOptionalFrom(); reply.setLocalPort(packet.getLocalPort()); reply.setRemotePort(packet.getRemotePort()); // this just sends the packet - no retries or whatnot @@ -348,17 +375,13 @@ class PacketHandler { * @param con null if unknown */ private void receivePing(Connection con, Packet packet) { - boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null); + SigningPublicKey spk = con != null ? con.getRemoteSPK() : null; + ByteArray ba = _cache.acquire(); + boolean ok = packet.verifySignature(_context, spk, ba.getData()); + _cache.release(ba); if (!ok) { - if (_log.shouldLog(Log.WARN)) { - if (packet.getOptionalFrom() == null) - _log.warn("Ping with no from (flagged? " + packet.isFlagSet(Packet.FLAG_FROM_INCLUDED) + ")"); - else if (packet.getOptionalSignature() == null) - _log.warn("Ping with no signature (flagged? " + packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED) + ")"); - else - _log.warn("Forged ping, discard (from=" + packet.getOptionalFrom().calculateHash().toBase64() - + " sig=" + packet.getOptionalSignature().toBase64() + ")"); - } + if (_log.shouldLog(Log.WARN)) + _log.warn("Bad ping, dropping: " + packet); } else { _manager.receivePing(con, packet); } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketLocal.java index b43018d6ca..346bd6e21f 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketLocal.java @@ -61,6 +61,25 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus { _lastSend = -1; _cancelledOn = -1; } + + /** + * This sets the from field in the packet to the Destination for the session + * provided in the constructor. + * This also sets flag FLAG_FROM_INCLUDED. + * Also, as of 0.9.39, sets the offline signing data if supported by the session. + * + * @since 0.9.39 moved from super + */ + public void setOptionalFrom() { + setFlag(FLAG_FROM_INCLUDED, true); + _optionFrom = _session.getMyDestination(); + if (_session.isOffline()) { + setFlag(FLAG_SIGNATURE_OFFLINE, true); + _transientExpires = _session.getOfflineExpiration(); + _transientSigningPublicKey = _session.getTransientSigningPublicKey(); + _offlineSignature = _session.getOfflineSignature(); + } + } public Destination getTo() { return _to; } @@ -227,11 +246,21 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus { //+ 1 // resendDelay //+ 2 // flags //+ 2 // optionSize - + 21 - + (_nacks != null ? 4*_nacks.length + 1 : 1) - + (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0) - + (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0) - + (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 0); + //+ 1 // nacksSize + + 22; + if (_nacks != null) + signatureOffset += 4 *_nacks.length; + if (isFlagSet(FLAG_DELAY_REQUESTED)) + signatureOffset += 2; + if (isFlagSet(FLAG_FROM_INCLUDED)) + signatureOffset += _optionFrom.size(); + if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) + signatureOffset += 2; + if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) { + signatureOffset += 6; + signatureOffset += _transientSigningPublicKey.length(); + signatureOffset += _offlineSignature.length(); + } System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, _optionSignature.length()); return size; } diff --git a/history.txt b/history.txt index 31b88952dd..6beb960ad7 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,18 @@ +2019-02-03 zzz + * I2CP: + - Remove revocation private key from CreateLeaseset2 message + - Use correct key to sign SessionConfig with offline keys + * Streaming: + - Support offline signatures (proposal 123) + - Don't send FROM in RESET, not required since 0.9.20 + - Send RESET when SYN signature verification fails + - Use cached buffers for signature verification + - Always verify packets with signatures, even if not required + * Test: Disable NTP in LocalClientManager + +2019-02-02 zzz + * Debian: Fix build of i2pcontrol + 2019-02-01 zzz * Debian: AppArmor fix for Oracle JVM (ticket #2319) * i2ptunnel: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index f6e6df3a5c..4e28a2c8d2 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 4; + public final static long BUILD = 5; /** for example "-test" */ public final static String EXTRA = "";