forked from I2P_Developers/i2p.i2p
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:
@ -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
|
||||
@ -873,6 +876,35 @@ class Connection {
|
||||
_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?
|
||||
* @return non-global stream sending ID, or 0 if unknown
|
||||
|
@ -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<Packet> _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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
||||
@ -162,6 +169,12 @@ class Packet {
|
||||
*/
|
||||
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;
|
||||
public static final int MIN_DELAY_CHOKE = 60001;
|
||||
@ -349,14 +362,11 @@ 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
|
||||
@ -697,29 +757,74 @@ class Packet {
|
||||
|
||||
/**
|
||||
* Determine whether the signature on the data is valid.
|
||||
* Packet MUST have a FROM option or will return false.
|
||||
*
|
||||
* @param ctx Application context
|
||||
* @param from the Destination the data came from
|
||||
* @param buffer data to validate with signature
|
||||
* @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 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();
|
||||
SigType type = spk.getType();
|
||||
if (type == null) {
|
||||
if (isFlagSet(FLAG_SIGNATURE_OFFLINE)) {
|
||||
if (_transientExpires < ctx.clock().now()) {
|
||||
Log l = ctx.logManager().getLog(Packet.class);
|
||||
if (l.shouldLog(Log.WARN))
|
||||
l.warn("Unknown sig type in " + from + " cannot verify " + toString());
|
||||
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 || !type.isAvailable()) {
|
||||
Log l = ctx.logManager().getLog(Packet.class);
|
||||
if (l.shouldLog(Log.WARN))
|
||||
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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -62,6 +62,25 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
|
||||
_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;
|
||||
}
|
||||
|
15
history.txt
15
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:
|
||||
|
@ -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 = "";
|
||||
|
Reference in New Issue
Block a user