diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 835309a5bc..75e843707c 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -59,6 +59,7 @@ public class TransportManager implements TransportEventListener { _context = context; _log = _context.logManager().getLog(TransportManager.class); _context.statManager().createRateStat("transport.banlistOnUnreachable", "Add a peer to the banlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("transport.banlistOnUsupportedSigType", "Add a peer to the banlist since signature type is unsupported", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("transport.noBidsYetNotAllUnreachable", "Add a peer to the banlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("transport.bidFailBanlisted", "Could not attempt to bid on message, as they were banlisted", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("transport.bidFailSelf", "Could not attempt to bid on message, as it targeted ourselves", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); @@ -499,8 +500,11 @@ public class TransportManager implements TransportEventListener { } } if (unreachableTransports >= _transports.size()) { - // Don't banlist if we aren't talking to anybody, as we may have a network connection issue - if (unreachableTransports >= _transports.size() && countActivePeers() > 0) { + if (msg.getTarget().getIdentity().getSigningPublicKey().getType() == null) { + _context.statManager().addRateData("transport.banlistOnUnsupportedSigType", 1); + _context.banlist().banlistRouterForever(peer, _x("Unsupported signature type")); + } else if (unreachableTransports >= _transports.size() && countActivePeers() > 0) { + // Don't banlist if we aren't talking to anybody, as we may have a network connection issue _context.statManager().addRateData("transport.banlistOnUnreachable", msg.getLifetime(), msg.getLifetime()); _context.banlist().banlistRouter(peer, _x("Unreachable on any transport")); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index ae504bb338..c7e4703a4d 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -362,6 +362,12 @@ public class NTCPTransport extends TransportImpl { return null; } + // Check for supported sig type + if (toAddress.getIdentity().getSigningPublicKey().getType() == null) { + markUnreachable(peer); + return null; + } + if (!allowConnection()) { if (_log.shouldLog(Log.WARN)) _log.warn("no bid when trying to send to " + peer + ", max connection limit reached"); diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java index 781fd57fec..6d1f177554 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; +import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataFormatException; @@ -47,6 +48,9 @@ class InboundEstablishState { private long _receivedSignedOnTime; private byte _receivedSignature[]; private boolean _verificationAttempted; + // sig not verified + private RouterIdentity _receivedUnconfirmedIdentity; + // identical to uncomfirmed, but sig now verified private RouterIdentity _receivedConfirmedIdentity; // general status private final long _establishBegin; @@ -295,9 +299,28 @@ class InboundEstablishState { if (cur == _receivedIdentity.length-1) { _receivedSignedOnTime = conf.readFinalFragmentSignedOnTime(); - if (_receivedSignature == null) - _receivedSignature = new byte[Signature.SIGNATURE_BYTES]; - conf.readFinalSignature(_receivedSignature, 0); + // TODO verify time to prevent replay attacks + buildIdentity(); + if (_receivedUnconfirmedIdentity != null) { + SigType type = _receivedUnconfirmedIdentity.getSigningPublicKey().getType(); + if (type != null) { + int sigLen = type.getSigLen(); + if (_receivedSignature == null) + _receivedSignature = new byte[sigLen]; + conf.readFinalSignature(_receivedSignature, 0, sigLen); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unsupported sig type from: " + toString()); + // _x() in UDPTransport + _context.banlist().banlistRouterForever(_receivedUnconfirmedIdentity.calculateHash(), + "Unsupported signature type"); + fail(); + } + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Bad ident from: " + toString()); + fail(); + } } if ( (_currentState == InboundState.IB_STATE_UNKNOWN) || @@ -318,9 +341,10 @@ class InboundEstablishState { */ private boolean confirmedFullyReceived() { if (_receivedIdentity != null) { - for (int i = 0; i < _receivedIdentity.length; i++) + for (int i = 0; i < _receivedIdentity.length; i++) { if (_receivedIdentity[i] == null) return false; + } return true; } else { return false; @@ -339,7 +363,51 @@ class InboundEstablishState { } return _receivedConfirmedIdentity; } - + + /** + * Construct Alice's RouterIdentity. + * Must have received all fragments. + * Sets _receivedUnconfirmedIdentity, unless invalid. + * + * Caller must synch on this. + * + * @since 0.9.16 was in verifyIdentity() + */ + private void buildIdentity() { + if (_receivedUnconfirmedIdentity != null) + return; // dup pkt? + int frags = _receivedIdentity.length; + byte[] ident; + if (frags > 1) { + int identSize = 0; + for (int i = 0; i < _receivedIdentity.length; i++) + identSize += _receivedIdentity[i].length; + ident = new byte[identSize]; + int off = 0; + for (int i = 0; i < _receivedIdentity.length; i++) { + int len = _receivedIdentity[i].length; + System.arraycopy(_receivedIdentity[i], 0, ident, off, len); + off += len; + } + } else { + // no need to copy + ident = _receivedIdentity[0]; + } + ByteArrayInputStream in = new ByteArrayInputStream(ident); + RouterIdentity peer = new RouterIdentity(); + try { + peer.readBytes(in); + _receivedUnconfirmedIdentity = peer; + } catch (DataFormatException dfe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Improperly formatted yet fully received ident", dfe); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Improperly formatted yet fully received ident", ioe); + } + } + + /** * Determine if Alice sent us a valid confirmation packet. The * identity signs: Alice's IP + Alice's port + Bob's IP + Bob's port @@ -351,21 +419,11 @@ class InboundEstablishState { * Caller must synch on this. */ private void verifyIdentity() { - int identSize = 0; - for (int i = 0; i < _receivedIdentity.length; i++) - identSize += _receivedIdentity[i].length; - byte ident[] = new byte[identSize]; - int off = 0; - for (int i = 0; i < _receivedIdentity.length; i++) { - int len = _receivedIdentity[i].length; - System.arraycopy(_receivedIdentity[i], 0, ident, off, len); - off += len; - } - ByteArrayInputStream in = new ByteArrayInputStream(ident); - RouterIdentity peer = new RouterIdentity(); - try { - peer.readBytes(in); - + if (_receivedUnconfirmedIdentity == null) + return; // either not yet recvd or bad ident + if (_receivedSignature == null) + return; // either not yet recvd or bad sig + byte signed[] = new byte[256+256 // X + Y + _aliceIP.length + 2 + _bobIP.length + 2 @@ -373,7 +431,7 @@ class InboundEstablishState { + 4 // signed on time ]; - off = 0; + int off = 0; System.arraycopy(_receivedX, 0, signed, off, _receivedX.length); off += _receivedX.length; getSentY(); @@ -391,22 +449,15 @@ class InboundEstablishState { off += 4; DataHelper.toLong(signed, off, 4, _receivedSignedOnTime); Signature sig = new Signature(_receivedSignature); - boolean ok = _context.dsa().verifySignature(sig, signed, peer.getSigningPublicKey()); + boolean ok = _context.dsa().verifySignature(sig, signed, _receivedUnconfirmedIdentity.getSigningPublicKey()); if (ok) { // todo partial spoof detection - get peer.calculateHash(), // lookup in netdb locally, if not equal, fail? - _receivedConfirmedIdentity = peer; + _receivedConfirmedIdentity = _receivedUnconfirmedIdentity; } else { if (_log.shouldLog(Log.WARN)) - _log.warn("Signature failed from " + peer); + _log.warn("Signature failed from " + _receivedUnconfirmedIdentity); } - } catch (DataFormatException dfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Improperly formatted yet fully received ident", dfe); - } catch (IOException ioe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Improperly formatted yet fully received ident", ioe); - } } private void packetReceived() { diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index bcb9603636..ff94852559 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -3,6 +3,7 @@ package net.i2p.router.transport.udp; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; +import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; @@ -41,6 +42,7 @@ class OutboundEstablishState { private SessionKey _sessionKey; private SessionKey _macKey; private Signature _receivedSignature; + // includes trailing padding to mod 16 private byte[] _receivedEncryptedSignature; private byte[] _receivedIV; // SessionConfirmed messages @@ -104,6 +106,7 @@ class OutboundEstablishState { /** * @param claimedAddress an IP/port based RemoteHostId, or null if unknown * @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect + * @param remotePeer must have supported sig type * @param introKey Bob's introduction key, as published in the netdb * @param addr non-null */ @@ -247,8 +250,20 @@ class OutboundEstablishState { _alicePort = reader.readPort(); _receivedRelayTag = reader.readRelayTag(); _receivedSignedOnTime = reader.readSignedOnTime(); - _receivedEncryptedSignature = new byte[Signature.SIGNATURE_BYTES + 8]; - reader.readEncryptedSignature(_receivedEncryptedSignature, 0); + // handle variable signature size + SigType type = _remotePeer.getSigningPublicKey().getType(); + if (type == null) { + // shouldn't happen, we only connect to supported peers + fail(); + packetReceived(); + return; + } + int sigLen = type.getSigLen(); + int mod = sigLen % 16; + int pad = (mod == 0) ? 0 : (16 - mod); + int esigLen = sigLen + pad; + _receivedEncryptedSignature = new byte[esigLen]; + reader.readEncryptedSignature(_receivedEncryptedSignature, 0, esigLen); _receivedIV = new byte[UDPPacket.IV_SIZE]; reader.readIV(_receivedIV, 0); @@ -353,7 +368,9 @@ class OutboundEstablishState { * decrypt the signature (and subsequent pad bytes) with the * additional layer of encryption using the negotiated key along side * the packet's IV + * * Caller must synch on this. + * Only call this once! Decrypts in-place. */ private void decryptSignature() { if (_receivedEncryptedSignature == null) throw new NullPointerException("encrypted signature is null! this=" + this.toString()); @@ -361,11 +378,20 @@ class OutboundEstablishState { if (_receivedIV == null) throw new NullPointerException("IV is null!"); _context.aes().decrypt(_receivedEncryptedSignature, 0, _receivedEncryptedSignature, 0, _sessionKey, _receivedIV, _receivedEncryptedSignature.length); - byte signatureBytes[] = new byte[Signature.SIGNATURE_BYTES]; - System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, Signature.SIGNATURE_BYTES); - _receivedSignature = new Signature(signatureBytes); + // handle variable signature size + SigType type = _remotePeer.getSigningPublicKey().getType(); + // if type == null throws NPE + int sigLen = type.getSigLen(); + int mod = sigLen % 16; + if (mod != 0) { + byte signatureBytes[] = new byte[sigLen]; + System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, sigLen); + _receivedSignature = new Signature(type, signatureBytes); + } else { + _receivedSignature = new Signature(type, _receivedEncryptedSignature); + } if (_log.shouldLog(Log.DEBUG)) - _log.debug("Decrypted received signature: " + Base64.encode(signatureBytes)); + _log.debug("Decrypted received signature: " + Base64.encode(_receivedSignature.getData())); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 7ce159a582..d518a0b568 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -596,14 +596,22 @@ class PacketBuilder { off += 4; DataHelper.toLong(data, off, 4, state.getSentSignedOnTime()); off += 4; - System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES); - off += Signature.SIGNATURE_BYTES; - // ok, we need another 8 bytes of random padding - // (ok, this only gives us 63 bits, not 64) - long l = _context.random().nextLong(); - if (l < 0) l = 0 - l; - DataHelper.toLong(data, off, 8, l); - off += 8; + + // handle variable signature size + Signature sig = state.getSentSignature(); + int siglen = sig.length(); + System.arraycopy(sig.getData(), 0, data, off, siglen); + off += siglen; + // ok, we need another few bytes of random padding + int rem = siglen % 16; + int padding; + if (rem > 0) { + padding = 16 - rem; + _context.random().nextBytes(data, off, padding); + off += padding; + } else { + padding = 0; + } if (_log.shouldLog(Log.DEBUG)) { StringBuilder buf = new StringBuilder(128); @@ -612,9 +620,9 @@ class PacketBuilder { buf.append(" Bob: ").append(Addresses.toString(state.getReceivedOurIP(), externalPort)); buf.append(" RelayTag: ").append(state.getSentRelayTag()); buf.append(" SignedOn: ").append(state.getSentSignedOnTime()); - buf.append(" signature: ").append(Base64.encode(state.getSentSignature().getData())); + buf.append(" signature: ").append(Base64.encode(sig.getData())); buf.append("\nRawCreated: ").append(Base64.encode(data, 0, off)); - buf.append("\nsignedTime: ").append(Base64.encode(data, off-8-Signature.SIGNATURE_BYTES-4, 4)); + buf.append("\nsignedTime: ").append(Base64.encode(data, off - padding - siglen - 4, 4)); _log.debug(buf.toString()); } @@ -623,7 +631,7 @@ class PacketBuilder { byte[] iv = SimpleByteCache.acquire(UDPPacket.IV_SIZE); _context.random().nextBytes(iv); - int encrWrite = Signature.SIGNATURE_BYTES + 8; + int encrWrite = siglen + padding; int sigBegin = off - encrWrite; _context.aes().encrypt(data, sigBegin, data, sigBegin, state.getCipherKey(), iv, encrWrite); @@ -774,8 +782,11 @@ class PacketBuilder { DataHelper.toLong(data, off, 4, state.getSentSignedOnTime()); off += 4; + // handle variable signature size // we need to pad this so we're at the encryption boundary - int mod = (off + Signature.SIGNATURE_BYTES) & 0x0f; + Signature sig = state.getSentSignature(); + int siglen = sig.length(); + int mod = (off + siglen) & 0x0f; if (mod != 0) { int paddingRequired = 16 - mod; // add an arbitrary number of 16byte pad blocks too ??? @@ -787,8 +798,8 @@ class PacketBuilder { // so trailing non-mod-16 data is ignored. That truncates the sig. // BUG: NPE here if null signature - System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES); - off += Signature.SIGNATURE_BYTES; + System.arraycopy(sig.getData(), 0, data, off, siglen); + off += siglen; } else { // We never get here (see above) diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java index affaf09df8..f7c89bb893 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java @@ -203,9 +203,10 @@ class UDPPacketReader { return rv; } - public void readEncryptedSignature(byte target[], int targetOffset) { + /** @param size the amount to be copied, including padding to mod 16 */ + public void readEncryptedSignature(byte target[], int targetOffset, int size) { int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2 + 4 + 4; - System.arraycopy(_message, offset, target, targetOffset, Signature.SIGNATURE_BYTES + 8); + System.arraycopy(_message, offset, target, targetOffset, size); } public void readIV(byte target[], int targetOffset) { @@ -239,7 +240,11 @@ class UDPPacketReader { System.arraycopy(_message, readOffset, target, targetOffset, len); } - /** read the time at which the signature was generated */ + /** + * Read the time at which the signature was generated. + * TODO must be completely in final fragment. + * Time and sig cannot be split across fragments. + */ public long readFinalFragmentSignedOnTime() { if (readCurrentFragmentNum() != readTotalFragmentNum()-1) throw new IllegalStateException("This is not the final fragment"); @@ -247,12 +252,19 @@ class UDPPacketReader { return DataHelper.fromLong(_message, readOffset, 4); } - /** read the signature from the final sessionConfirmed packet */ - public void readFinalSignature(byte target[], int targetOffset) { + /** + * Read the signature from the final sessionConfirmed packet. + * TODO must be completely in final fragment. + * Time and sig cannot be split across fragments. + * @param size not including padding + */ + public void readFinalSignature(byte target[], int targetOffset, int size) { if (readCurrentFragmentNum() != readTotalFragmentNum()-1) throw new IllegalStateException("This is not the final fragment"); - int readOffset = _payloadBeginOffset + _payloadLength - Signature.SIGNATURE_BYTES; - System.arraycopy(_message, readOffset, target, targetOffset, Signature.SIGNATURE_BYTES); + int readOffset = _payloadBeginOffset + _payloadLength - size; + if (readOffset < readBodyOffset() + (1 + 2 + 4)) + throw new IllegalStateException("Sig split across fragments"); + System.arraycopy(_message, readOffset, target, targetOffset, size); } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 34976301c5..63fc957600 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1550,6 +1550,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return null; } + // Check for supported sig type + if (toAddress.getIdentity().getSigningPublicKey().getType() == null) { + markUnreachable(to); + return null; + } + if (!allowConnection()) return _cachedBid[TRANSIENT_FAIL_BID];