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
Move setOptionalFrom() from Packet to PacketLocal
Always verify packets with signatures, even if not required
AIOOBE checks
cleanups, log tweaks
This commit is contained in:
zzz
2019-02-03 13:41:42 +00:00
parent 7d11fb269e
commit 0e710f8785
9 changed files with 333 additions and 79 deletions

View File

@ -15,6 +15,7 @@ import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PSocketException; import net.i2p.client.streaming.I2PSocketException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer;
import net.i2p.util.SimpleTimer2; import net.i2p.util.SimpleTimer2;
@ -30,6 +31,7 @@ class Connection {
private final ConnectionManager _connectionManager; private final ConnectionManager _connectionManager;
private final I2PSession _session; private final I2PSession _session;
private Destination _remotePeer; private Destination _remotePeer;
private SigningPublicKey _transientSPK;
private final AtomicLong _sendStreamId = new AtomicLong(); private final AtomicLong _sendStreamId = new AtomicLong();
private final AtomicLong _receiveStreamId = new AtomicLong(); private final AtomicLong _receiveStreamId = new AtomicLong();
private volatile long _lastSendTime; private volatile long _lastSendTime;
@ -327,8 +329,9 @@ class Connection {
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setSendStreamId(_sendStreamId.get()); reply.setSendStreamId(_sendStreamId.get());
reply.setReceiveStreamId(_receiveStreamId.get()); reply.setReceiveStreamId(_receiveStreamId.get());
// TODO remove this someday, as of 0.9.20 we do not require it // As of 0.9.20 we do not require FROM
reply.setOptionalFrom(); // Removed in 0.9.39
//reply.setOptionalFrom();
reply.setLocalPort(_localPort); reply.setLocalPort(_localPort);
reply.setRemotePort(_remotePort); reply.setRemotePort(_remotePort);
// this just sends the packet - no retries or whatnot // 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 // now that we know who the other end is, get the rtt etc. from the cache
_connectionManager.updateOptsFromShare(this); _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? * What stream do we send data to the peer on?

View File

@ -7,7 +7,9 @@ import java.util.concurrent.TimeUnit;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.client.streaming.RouterRestartException; import net.i2p.client.streaming.RouterRestartException;
import net.i2p.data.ByteArray;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.util.ByteCache;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer;
import net.i2p.util.SimpleTimer2; import net.i2p.util.SimpleTimer2;
@ -23,6 +25,7 @@ import net.i2p.util.SimpleTimer2;
class ConnectionHandler { class ConnectionHandler {
private final I2PAppContext _context; private final I2PAppContext _context;
private final Log _log; private final Log _log;
private final ByteCache _cache = ByteCache.getInstance(32, 4*1024);
private final ConnectionManager _manager; private final ConnectionManager _manager;
private final LinkedBlockingQueue<Packet> _synQueue; private final LinkedBlockingQueue<Packet> _synQueue;
private final SimpleTimer2 _timer; 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) { 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 (!ok) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldWarn())
_log.warn("Received a spoofed SYN packet: they said they were " + packet.getOptionalFrom()); _log.warn("Can't send reset in response to packet: " + packet);
return; return;
} }
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom(), packet.getSession()); PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom(), packet.getSession());
reply.setFlag(Packet.FLAG_RESET); reply.setFlag(Packet.FLAG_RESET | Packet.FLAG_SIGNATURE_INCLUDED);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(packet.getSequenceNum()); reply.setAckThrough(packet.getSequenceNum());
reply.setSendStreamId(packet.getReceiveStreamId()); reply.setSendStreamId(packet.getReceiveStreamId());
reply.setReceiveStreamId(0); reply.setReceiveStreamId(0);
// TODO remove this someday, as of 0.9.20 we do not require it // As of 0.9.20 we do not require FROM
reply.setOptionalFrom(); // Removed in 0.9.39
//reply.setOptionalFrom();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending RST: " + reply + " because of " + packet); _log.debug("Sending RST: " + reply + " because of " + packet);
// this just sends the packet - no retries or whatnot // this just sends the packet - no retries or whatnot

View File

@ -4,8 +4,11 @@ import java.util.List;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer;
@ -23,6 +26,7 @@ import net.i2p.util.SimpleTimer;
class ConnectionPacketHandler { class ConnectionPacketHandler {
private final I2PAppContext _context; private final I2PAppContext _context;
private final Log _log; private final Log _log;
private final ByteCache _cache = ByteCache.getInstance(32, 4*1024);
public static final int MAX_SLOW_START_WINDOW = 24; public static final int MAX_SLOW_START_WINDOW = 24;
@ -514,6 +518,7 @@ class ConnectionPacketHandler {
if (con.getSendStreamId() <= 0) { if (con.getSendStreamId() <= 0) {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
// this is an incoming connection.
con.setSendStreamId(packet.getReceiveStreamId()); con.setSendStreamId(packet.getReceiveStreamId());
Destination dest = packet.getOptionalFrom(); Destination dest = packet.getOptionalFrom();
if (dest == null) { if (dest == null) {
@ -522,6 +527,9 @@ class ConnectionPacketHandler {
return false; return false;
} }
con.setRemotePeer(dest); con.setRemotePeer(dest);
SigningPublicKey spk = packet.getTransientSPK();
if (spk != null)
con.setRemoteTransientSPK(spk);
return true; return true;
} else { } else {
// neither RST nor SYN and we dont have the stream id yet? // neither RST nor SYN and we dont have the stream id yet?
@ -535,6 +543,7 @@ class ConnectionPacketHandler {
} }
} }
} else { } else {
// getting a lot of these - why? mostly/all for acks...
if (con.getSendStreamId() != packet.getReceiveStreamId()) { if (con.getSendStreamId() != packet.getReceiveStreamId()) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Packet received with the wrong reply stream id: " _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, * Prior to 0.9.20, the reset packet must contain a FROM field,
* and we used that for verification. * and we used that for verification.
* As of 0.9.20, we correctly use the connection's remote peer. * As of 0.9.20, we correctly use the connection's remote peer.
*
* @param con non-null
*/ */
private void verifyReset(Packet packet, Connection con) { private void verifyReset(Packet packet, Connection con) {
if (con.getReceiveStreamId() == packet.getSendStreamId()) { if (con.getReceiveStreamId() == packet.getSendStreamId()) {
Destination from = con.getRemotePeer(); SigningPublicKey spk = con.getRemoteSPK();
if (from == null) ByteArray ba = _cache.acquire();
from = packet.getOptionalFrom(); boolean ok = packet.verifySignature(_context, spk, ba.getData());
boolean ok = packet.verifySignature(_context, from, null); _cache.release(ba);
if (!ok) { if (!ok) {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("Received unsigned / forged RST on " + con); _log.error("Received unsigned / forged RST on " + con);
return; return;
} else { } else {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldWarn())
_log.debug("Reset received"); _log.warn("Reset received on " + con);;
// ok, valid RST // ok, valid RST
con.resetReceived(); con.resetReceived();
con.eventOccurred(); con.eventOccurred();
@ -587,17 +598,18 @@ class ConnectionPacketHandler {
/** /**
* Verify the signature if necessary. * Verify the signature if necessary.
* *
* @param con non-null
* @throws I2PException if the signature was necessary and it was invalid * @throws I2PException if the signature was necessary and it was invalid
*/ */
private void verifySignature(Packet packet, Connection con) throws I2PException { private void verifySignature(Packet packet, Connection con) throws I2PException {
// verify the signature if necessary // verify the signature if necessary
if (con.getOptions().getRequireFullySigned() || 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 // we need a valid signature
Destination from = con.getRemotePeer(); SigningPublicKey spk = con.getRemoteSPK();
if (from == null) ByteArray ba = _cache.acquire();
from = packet.getOptionalFrom(); boolean sigOk = packet.verifySignature(_context, spk, null);
boolean sigOk = packet.verifySignature(_context, from, null); _cache.release(ba);
if (!sigOk) { if (!sigOk) {
throw new I2PException("Received unsigned / forged packet: " + packet); throw new I2PException("Received unsigned / forged packet: " + packet);
} }

View File

@ -77,6 +77,10 @@ class MessageHandler implements I2PSessionMuxedListener {
packet.setRemotePort(fromPort); packet.setRemotePort(fromPort);
packet.setLocalPort(toPort); packet.setLocalPort(toPort);
_manager.getPacketHandler().receivePacket(packet); _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) { } catch (IllegalArgumentException iae) {
_context.statManager().addRateData("stream.packetReceiveFailure", 1); _context.statManager().addRateData("stream.packetReceiveFailure", 1);
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))

View File

@ -1,8 +1,10 @@
package net.i2p.client.streaming.impl; package net.i2p.client.streaming.impl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession; import net.i2p.client.I2PSession;
@ -82,6 +84,11 @@ class Packet {
protected Destination _optionFrom; protected Destination _optionFrom;
private int _optionDelay; private int _optionDelay;
private int _optionMaxSize; private int _optionMaxSize;
// following 3 for ofline sigs
protected long _transientExpires;
protected Signature _offlineSignature;
protected SigningPublicKey _transientSigningPublicKey;
// ports
private int _localPort; private int _localPort;
private int _remotePort; private int _remotePort;
@ -161,6 +168,12 @@ class Packet {
* If set, this packet doesn't really want to ack anything * If set, this packet doesn't really want to ack anything
*/ */
public static final int FLAG_NO_ACK = (1 << 10); 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; public static final int DEFAULT_MAX_SIZE = 32*1024;
protected static final int MAX_DELAY_REQUEST = 65535; protected static final int MAX_DELAY_REQUEST = 65535;
@ -348,15 +361,12 @@ class Packet {
*/ */
public Destination getOptionalFrom() { return _optionFrom; } public Destination getOptionalFrom() { return _optionFrom; }
/** /**
* This sets the from field in the packet to the Destination for the session * Only if an offline signing block was included, else null
* provided in the constructor. *
* This also sets flag FLAG_FROM_INCLUDED * @since 0.9.39
*/ */
public void setOptionalFrom() { public SigningPublicKey getTransientSPK() { return _transientSigningPublicKey; }
setFlag(FLAG_FROM_INCLUDED, true);
_optionFrom = _session.getMyDestination();
}
/** /**
* How many milliseconds the sender of this packet wants the recipient * How many milliseconds the sender of this packet wants the recipient
@ -478,6 +488,11 @@ class Packet {
optionSize += _optionFrom.size(); optionSize += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
optionSize += 2; optionSize += 2;
if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) {
optionSize += 6;
optionSize += _transientSigningPublicKey.length();
optionSize += _offlineSignature.length();
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (fakeSigLen > 0) if (fakeSigLen > 0)
optionSize += fakeSigLen; optionSize += fakeSigLen;
@ -501,6 +516,18 @@ class Packet {
DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE); DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE);
cur += 2; 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 (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (fakeSigLen == 0) { if (fakeSigLen == 0) {
// we're signing (or validating) // we're signing (or validating)
@ -535,9 +562,8 @@ class Packet {
/** /**
* how large would this packet be if we wrote it * 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() { private int writtenSize() {
//int size = 0; //int size = 0;
@ -564,6 +590,11 @@ class Packet {
size += 2; size += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
size += _optionSignature.length(); size += _optionSignature.length();
if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) {
size += 6;
size += _transientSigningPublicKey.length();
size += _offlineSignature.length();
}
if (_payload != null) { if (_payload != null) {
size += _payload.getValid(); size += _payload.getValid();
@ -583,6 +614,7 @@ class Packet {
* part of the packet? * part of the packet?
* *
* @throws IllegalArgumentException if the data is b0rked * @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 { public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException {
if (buffer.length - offset < length) if (buffer.length - offset < length)
@ -657,11 +689,39 @@ class Packet {
setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2)); setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2));
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)) { if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
Signature optionSignature; Signature optionSignature;
Destination from = getOptionalFrom(); if (_optionFrom != null) {
if (from != null) { SigType type;
optionSignature = new Signature(from.getSigningPublicKey().getType()); if (isFlagSet(FLAG_SIGNATURE_OFFLINE))
type = _transientSigningPublicKey.getType();
else
type = _optionFrom.getSigningPublicKey().getType();
optionSignature = new Signature(type);
} else { } else {
// super cheat for now, look for correct type, // super cheat for now, look for correct type,
// assume no more options. If we add to the options // assume no more options. If we add to the options
@ -694,32 +754,77 @@ class Packet {
cur += buf.length; 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. * Determine whether the signature on the data is valid.
* *
* @param ctx Application context * @param ctx Application context
* @param from the Destination the data came from * @param spk Signing key to verify with, ONLY if there is no FROM field in this packet.
* @param buffer data to validate with signature * 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, * @return true if the signature exists and validates against the data,
* false otherwise. * 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 (!isFlagSet(FLAG_SIGNATURE_INCLUDED)) return false;
if (_optionSignature == null) return false; if (_optionSignature == null) return false;
SigningPublicKey spk = _optionFrom != null ? _optionFrom.getSigningPublicKey() : altSPK;
// prevent receiveNewSyn() ... !active ... sendReset() ... verifySignature ... NPE // prevent receiveNewSyn() ... !active ... sendReset() ... verifySignature ... NPE
if (from == null) return false; if (spk == null) return false;
int size = writtenSize(); int size = writtenSize();
if (buffer == null) if (buffer == null)
buffer = new byte[size]; 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(); SigType type = spk.getType();
if (type == null) { if (type == null || !type.isAvailable()) {
Log l = ctx.logManager().getLog(Packet.class); Log l = ctx.logManager().getLog(Packet.class);
if (l.shouldLog(Log.WARN)) 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; return false;
} }
int written = writePacket(buffer, 0, type.getSigLen()); 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 // 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 // on a close or reset packet where we have a signature without a FROM
if (type != _optionSignature.getType() && 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()); _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) { if (!ok) {
Log l = ctx.logManager().getLog(Packet.class); Log l = ctx.logManager().getLog(Packet.class);
if (l.shouldLog(Log.WARN)) if (l.shouldLog(Log.WARN))
l.warn("Signature failed on " + toString(), new Exception("moo")); l.warn("Signature failed on " + toString() + " using SPK " + spk);
//if (false) {
// l.error(Base64.encode(buffer, 0, size));
// l.error("Signature: " + Base64.encode(_optionSignature.getData()));
//}
} }
return ok; return ok;
} }
@ -776,6 +890,11 @@ class Packet {
} }
private final void toFlagString(StringBuilder buf) { 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)) if (isFlagSet(FLAG_NO_ACK))
buf.append(" NO_ACK"); buf.append(" NO_ACK");
else else
@ -786,21 +905,30 @@ class Packet {
buf.append(' ').append(_nacks[i]); 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_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_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS ").append(_optionMaxSize);
if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE"); 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 (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (_optionSignature != null) if (_optionSignature != null)
buf.append(" SIG ").append(_optionSignature.length()); buf.append(" SIG ").append(_optionSignature.getType());
else else
buf.append(" (to be signed)"); 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, /** Generate a pcap/tcpdump-compatible format,

View File

@ -5,7 +5,10 @@ import java.util.Date;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.data.ByteArray;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -18,6 +21,7 @@ class PacketHandler {
private final ConnectionManager _manager; private final ConnectionManager _manager;
private final I2PAppContext _context; private final I2PAppContext _context;
private final Log _log; private final Log _log;
private final ByteCache _cache = ByteCache.getInstance(32, 4*1024);
//private int _lastDelay; //private int _lastDelay;
//private int _dropped; //private int _dropped;
@ -179,8 +183,11 @@ class PacketHandler {
long oldId = con.getSendStreamId(); long oldId = con.getSendStreamId();
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
if (oldId <= 0) { if (oldId <= 0) {
// con fully established, w00t // outgoing con now fully established
con.setSendStreamId(packet.getReceiveStreamId()); con.setSendStreamId(packet.getReceiveStreamId());
SigningPublicKey spk = packet.getTransientSPK();
if (spk != null)
con.setRemoteTransientSPK(spk);
} else if (oldId == packet.getReceiveStreamId()) { } else if (oldId == packet.getReceiveStreamId()) {
// ok, as expected... // ok, as expected...
} else { } else {
@ -195,9 +202,14 @@ class PacketHandler {
try { try {
con.getPacketHandler().receivePacket(packet, con); con.getPacketHandler().receivePacket(packet, con);
} catch (I2PException ie) { } catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldWarn())
_log.error("Received forged packet for " + con + "/" + oldId + ": " + packet, ie); _log.warn("Sig verify fail for " + con + "/" + oldId + ": " + packet, ie);
con.setSendStreamId(oldId); 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)) { } else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
@ -238,19 +250,34 @@ class PacketHandler {
Destination from = packet.getOptionalFrom(); Destination from = packet.getOptionalFrom();
if (from == null) if (from == null)
return; 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 (!ok) {
if (_log.shouldLog(Log.WARN)) 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; 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_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setSendStreamId(packet.getReceiveStreamId()); reply.setSendStreamId(packet.getReceiveStreamId());
reply.setReceiveStreamId(packet.getSendStreamId()); reply.setReceiveStreamId(packet.getSendStreamId());
// TODO remove this someday, as of 0.9.20 we do not require it // As of 0.9.20 we do not require FROM
reply.setOptionalFrom(); // Removed in 0.9.39
//reply.setOptionalFrom();
reply.setLocalPort(packet.getLocalPort()); reply.setLocalPort(packet.getLocalPort());
reply.setRemotePort(packet.getRemotePort()); reply.setRemotePort(packet.getRemotePort());
// this just sends the packet - no retries or whatnot // this just sends the packet - no retries or whatnot
@ -348,17 +375,13 @@ class PacketHandler {
* @param con null if unknown * @param con null if unknown
*/ */
private void receivePing(Connection con, Packet packet) { 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 (!ok) {
if (_log.shouldLog(Log.WARN)) { if (_log.shouldLog(Log.WARN))
if (packet.getOptionalFrom() == null) _log.warn("Bad ping, dropping: " + packet);
_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() + ")");
}
} else { } else {
_manager.receivePing(con, packet); _manager.receivePing(con, packet);
} }

View File

@ -61,6 +61,25 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
_lastSend = -1; _lastSend = -1;
_cancelledOn = -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; } public Destination getTo() { return _to; }
@ -227,11 +246,21 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
//+ 1 // resendDelay //+ 1 // resendDelay
//+ 2 // flags //+ 2 // flags
//+ 2 // optionSize //+ 2 // optionSize
+ 21 //+ 1 // nacksSize
+ (_nacks != null ? 4*_nacks.length + 1 : 1) + 22;
+ (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0) if (_nacks != null)
+ (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0) signatureOffset += 4 *_nacks.length;
+ (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 0); 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()); System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, _optionSignature.length());
return size; return size;
} }

View File

@ -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 2019-02-01 zzz
* Debian: AppArmor fix for Oracle JVM (ticket #2319) * Debian: AppArmor fix for Oracle JVM (ticket #2319)
* i2ptunnel: * i2ptunnel:

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 4; public final static long BUILD = 5;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";