From 46ef49f2cf0786d2ffcdfe36cb8ce4310a2dbf22 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Mon, 28 Feb 2022 16:24:59 -0500 Subject: [PATCH] SSU2: Token Req. and Retry fixes Fix Token Request and Retry payload generation Implement Token Request and Retry payload decryption Decrypt payloads in-place Change from numbers to constants --- .../transport/udp/InboundEstablishState2.java | 46 +++++++++++-------- .../udp/OutboundEstablishState2.java | 32 ++++++++++--- .../router/transport/udp/PacketBuilder2.java | 12 ++--- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java index dca920afe6..178ccae628 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java @@ -9,6 +9,7 @@ import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.List; +import com.southernstorm.noise.protocol.ChaChaPolyCipherState; import com.southernstorm.noise.protocol.CipherState; import com.southernstorm.noise.protocol.CipherStatePair; import com.southernstorm.noise.protocol.HandshakeState; @@ -23,6 +24,7 @@ import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterInfo; import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import static net.i2p.router.transport.udp.SSU2Util.*; import net.i2p.util.Addresses; import net.i2p.util.Log; @@ -80,20 +82,27 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa int len = pkt.getLength(); byte data[] = pkt.getData(); _rcvConnID = DataHelper.fromLong8(data, off); - _sendConnID = DataHelper.fromLong8(data, off + 16); + _sendConnID = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET); if (_rcvConnID == _sendConnID) throw new GeneralSecurityException("Identical Conn IDs"); - int type = data[off + 12] & 0xff; - long token = DataHelper.fromLong8(data, off + 24); - if (type == 10) { + int type = data[off + TYPE_OFFSET] & 0xff; + long token = DataHelper.fromLong8(data, off + TOKEN_OFFSET); + if (type == TOKEN_REQUEST_FLAG_BYTE) { _currentState = InboundState.IB_STATE_TOKEN_REQUEST_RECEIVED; - // TODO decrypt chacha? + // decrypt in-place + ChaChaPolyCipherState chacha = new ChaChaPolyCipherState(); + chacha.initializeKey(_rcvHeaderEncryptKey1, 0); + long n = DataHelper.fromLong(data, off + PKT_NUM_OFFSET, 4); + chacha.setNonce(n); + chacha.decryptWithAd(data, off, LONG_HEADER_SIZE, + data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE); + processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + MAC_LEN), true); _sendHeaderEncryptKey2 = introKey; do { token = ctx.random().nextLong(); } while (token == 0); _token = token; - } else if (type == 0 && + } else if (type == SESSION_REQUEST_FLAG_BYTE && (token == 0 || (ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) { _currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED; @@ -104,20 +113,21 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa _token = token; } else { // fast MSB check for key < 2^255 - if ((data[off + 32 + 32 - 1] & 0x80) != 0) + if ((data[off + LONG_HEADER_SIZE + KEY_LEN - 1] & 0x80) != 0) throw new GeneralSecurityException("Bad PK msg 1"); // probably don't need again _token = token; _handshakeState.start(); if (_log.shouldDebug()) _log.debug("State after start: " + _handshakeState); - _handshakeState.mixHash(data, off, 32); + _handshakeState.mixHash(data, off, LONG_HEADER_SIZE); if (_log.shouldDebug()) _log.debug("State after mixHash 1: " + _handshakeState); byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC + // decrypt in-place try { - _handshakeState.readMessage(data, off + 32, len - 32, payload, 0); + _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE); } catch (GeneralSecurityException gse) { if (_log.shouldDebug()) _log.debug("Session request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse); @@ -125,7 +135,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } if (_log.shouldDebug()) _log.debug("State after sess req: " + _handshakeState); - processPayload(payload, payload.length, true); + processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true); _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader"); _currentState = InboundState.IB_STATE_REQUEST_RECEIVED; } @@ -135,12 +145,12 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa @Override public int getVersion() { return 2; } - private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException { + private void processPayload(byte[] payload, int offset, int length, boolean isHandshake) throws GeneralSecurityException { try { - int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake); + int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, isHandshake); System.out.println("Processed " + blocks + " blocks"); } catch (Exception e) { - _log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length)); + _log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length), e); throw new GeneralSecurityException("IES2 payload error", e); } } @@ -363,9 +373,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa if (_log.shouldDebug()) _log.debug("State after mixHash 1: " + _handshakeState); - byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC + // decrypt in-place try { - _handshakeState.readMessage(data, off + 32, len - 32, payload, 0); + _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE); } catch (GeneralSecurityException gse) { if (_log.shouldDebug()) _log.debug("Session Request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse); @@ -373,7 +383,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } if (_log.shouldDebug()) _log.debug("State after sess req: " + _handshakeState); - processPayload(payload, payload.length, true); + processPayload(data, off + LONG_HEADER_SIZE, len - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN), true); _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader"); _currentState = InboundState.IB_STATE_REQUEST_RECEIVED; @@ -408,7 +418,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC try { - _handshakeState.readMessage(data, off + 16, len - 16, payload, 0); + _handshakeState.readMessage(data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE, payload, 0); } catch (GeneralSecurityException gse) { if (_log.shouldDebug()) _log.debug("Session Confirmed error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse); @@ -416,7 +426,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } if (_log.shouldDebug()) _log.debug("State after sess conf: " + _handshakeState); - processPayload(payload, payload.length, false); + processPayload(payload, 0, payload.length, false); _sessCrForReTX = null; // TODO split, calculate keys diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java index 2b11a019ee..ef27d2a03c 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java @@ -7,6 +7,7 @@ import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.GeneralSecurityException; +import com.southernstorm.noise.protocol.ChaChaPolyCipherState; import com.southernstorm.noise.protocol.CipherState; import com.southernstorm.noise.protocol.CipherStatePair; import com.southernstorm.noise.protocol.HandshakeState; @@ -20,6 +21,7 @@ import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.router.RouterContext; +import static net.i2p.router.transport.udp.SSU2Util.*; import net.i2p.util.Addresses; import net.i2p.util.Log; @@ -156,9 +158,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl _rcvHeaderEncryptKey2 = null; } - private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException { + private void processPayload(byte[] payload, int offset, int length, boolean isHandshake) throws GeneralSecurityException { try { - int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake); + int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, isHandshake); System.out.println("Processed " + blocks + " blocks"); } catch (Exception e) { throw new GeneralSecurityException("Session Created payload error", e); @@ -264,6 +266,24 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl public synchronized void receiveRetry(UDPPacket packet) throws GeneralSecurityException { ////// TODO state check + DatagramPacket pkt = packet.getPacket(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + byte data[] = pkt.getData(); + try { + // decrypt in-place + ChaChaPolyCipherState chacha = new ChaChaPolyCipherState(); + chacha.initializeKey(_rcvHeaderEncryptKey1, 0); + long n = DataHelper.fromLong(data, off + PKT_NUM_OFFSET, 4); + chacha.setNonce(n); + chacha.decryptWithAd(data, off, LONG_HEADER_SIZE, + data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE); + processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + MAC_LEN), true); + } catch (GeneralSecurityException gse) { + if (_log.shouldDebug()) + _log.debug("Retry error", gse); + throw gse; + } createNewState(_routerAddress); ////// TODO state change } @@ -283,13 +303,13 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl int off = pkt.getOffset(); int len = pkt.getLength(); byte data[] = pkt.getData(); - _handshakeState.mixHash(data, off, 32); + _handshakeState.mixHash(data, off, LONG_HEADER_SIZE); if (_log.shouldDebug()) _log.debug("State after mixHash 2: " + _handshakeState); - byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC + // decrypt in-place try { - _handshakeState.readMessage(data, off + 32, len - 32, payload, 0); + _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE); } catch (GeneralSecurityException gse) { if (_log.shouldDebug()) _log.debug("Session create error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse); @@ -297,7 +317,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } if (_log.shouldDebug()) _log.debug("State after sess cr: " + _handshakeState); - processPayload(payload, payload.length, true); + processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true); _sessReqForReTX = null; _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed"); diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java index cf7c157a28..2c6263116e 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -839,7 +839,7 @@ class PacketBuilder2 { byte data[] = pkt.getData(); int off = pkt.getOffset(); try { - List<Block> blocks = new ArrayList<Block>(4); + List<Block> blocks = new ArrayList<Block>(3); Block block = new SSU2Payload.DateTimeBlock(_context); int len = block.getTotalLength(); blocks.add(block); @@ -850,8 +850,7 @@ class PacketBuilder2 { block = getPadding(len, 1280); len += block.getTotalLength(); blocks.add(block); - byte[] payload = new byte[len]; - SSU2Payload.writePayload(payload, 0, blocks); + SSU2Payload.writePayload(data, off + LONG_HEADER_SIZE, blocks); ChaChaPolyCipherState chacha = new ChaChaPolyCipherState(); chacha.initializeKey(chachaKey, 0); @@ -881,7 +880,7 @@ class PacketBuilder2 { byte data[] = pkt.getData(); int off = pkt.getOffset(); try { - List<Block> blocks = new ArrayList<Block>(4); + List<Block> blocks = new ArrayList<Block>(2); Block block = new SSU2Payload.DateTimeBlock(_context); int len = block.getTotalLength(); blocks.add(block); @@ -889,14 +888,13 @@ class PacketBuilder2 { block = getPadding(len, 1280); len += block.getTotalLength(); blocks.add(block); - byte[] payload = new byte[len]; - SSU2Payload.writePayload(payload, 0, blocks); + SSU2Payload.writePayload(data, off + LONG_HEADER_SIZE, blocks); ChaChaPolyCipherState chacha = new ChaChaPolyCipherState(); chacha.initializeKey(chachaKey, 0); chacha.setNonce(n); chacha.encryptWithAd(data, off, LONG_HEADER_SIZE, - data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE); + data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len); pkt.setLength(pkt.getLength() + len + MAC_LEN); } catch (RuntimeException re) { -- GitLab