diff --git a/router/java/src/net/i2p/data/i2np/GarlicClove.java b/router/java/src/net/i2p/data/i2np/GarlicClove.java index a0e395a8394068c3ea2af38190af1c4c865705fa..218f23579359dba135b4a384dbbf24bfba2ba8dc 100644 --- a/router/java/src/net/i2p/data/i2np/GarlicClove.java +++ b/router/java/src/net/i2p/data/i2np/GarlicClove.java @@ -30,7 +30,6 @@ import net.i2p.util.Log; */ public class GarlicClove extends DataStructureImpl { - //private final Log _log; private static final long serialVersionUID = 1L; private transient final I2PAppContext _context; private DeliveryInstructions _instructions; @@ -41,7 +40,6 @@ public class GarlicClove extends DataStructureImpl { public GarlicClove(I2PAppContext context) { _context = context; - //_log = context.logManager().getLog(GarlicClove.class); _cloveId = -1; } @@ -66,14 +64,12 @@ public class GarlicClove extends DataStructureImpl { } /** - * + * @return length read */ public int readBytes(byte source[], int offset) throws DataFormatException { int cur = offset; _instructions = DeliveryInstructions.create(source, offset); cur += _instructions.getSize(); - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Read instructions: " + _instructions); try { I2NPMessageHandler handler = new I2NPMessageHandler(_context); cur += handler.readMessage(source, cur); @@ -85,17 +81,31 @@ public class GarlicClove extends DataStructureImpl { cur += 4; _expiration = DataHelper.fromDate(source, cur); cur += DataHelper.DATE_LENGTH; - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration); - //_certificate = new Certificate(); - //cur += _certificate.readBytes(source, cur); _certificate = Certificate.create(source, cur); cur += _certificate.size(); - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Read cert: " + _certificate); return cur - offset; } + /** + * Short format for ECIES-Ratchet, saves 22 bytes. + * NTCP2-style header, no ID, no separate expiration, no cert. + * + * @since 0.9.44 + */ + public void readBytesRatchet(byte source[], int offset, int len) throws DataFormatException { + _instructions = DeliveryInstructions.create(source, offset); + int isz = _instructions.getSize(); + try { + I2NPMessageHandler handler = new I2NPMessageHandler(_context); + _msg = I2NPMessageImpl.fromRawByteArrayNTCP2(_context, source, offset + isz, len - isz, handler); + _cloveId = _msg.getUniqueId(); + _expiration = new Date(_msg.getMessageExpiration()); + _certificate = Certificate.NULL_CERT; + } catch (I2NPMessageException ime) { + throw new DataFormatException("Unable to read the message from a garlic clove", ime); + } + } + /** * @deprecated unused, use byte array method to avoid copying * @throws UnsupportedOperationException always @@ -111,16 +121,8 @@ public class GarlicClove extends DataStructureImpl { @Override public byte[] toByteArray() { byte rv[] = new byte[estimateSize()]; - int offset = 0; - offset += _instructions.writeBytes(rv, offset); - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Wrote instructions: " + _instructions); - //offset += _msg.toByteArray(rv); - try { - byte m[] = _msg.toByteArray(); - System.arraycopy(m, 0, rv, offset, m.length); - offset += m.length; - } catch (RuntimeException e) { throw new RuntimeException("Unable to write: " + _msg + ": " + e.getMessage()); } + int offset = _instructions.writeBytes(rv, 0); + offset = _msg.toByteArray(rv, offset); DataHelper.toLong(rv, offset, 4, _cloveId); offset += 4; DataHelper.toDate(rv, offset, _expiration.getTime()); @@ -132,6 +134,28 @@ public class GarlicClove extends DataStructureImpl { } return rv; } + + /** + * Short format for ECIES-Ratchet, saves 22 bytes. + * NTCP2-style header, no ID, no separate expiration, no cert. + * + * @return new offset + * @since 0.9.44 + */ + public int writeBytesRatchet(byte[] tgt, int offset) { + // returns length written + offset += _instructions.writeBytes(tgt, offset); + // returns new offset + offset = _msg.toRawByteArrayNTCP2(tgt, offset); + return offset; + } + + /** + * @since 0.9.44 + */ + public int getSizeRatchet() { + return _instructions.getSize() + _msg.getMessageSize() - 7; + } public int estimateSize() { return _instructions.getSize() diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java index 6fef9901f8e98e3a7af3502d6073ff41003acc22..169f87ef5b426838550c40f5fb1a5c1902bcfc19 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -19,6 +19,7 @@ import net.i2p.crypto.EncType; import net.i2p.crypto.HKDF; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.Base64; +import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; @@ -26,7 +27,9 @@ import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; import net.i2p.data.SessionTag; +import net.i2p.data.i2np.GarlicClove; import static net.i2p.router.crypto.ratchet.RatchetPayload.*; +import net.i2p.router.message.CloveSet; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; @@ -122,7 +125,7 @@ public final class ECIESAEADEngine { * * @return decrypted data or null on failure */ - public byte[] decrypt(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager) throws DataFormatException { + public CloveSet decrypt(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager) throws DataFormatException { if (targetPrivateKey.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); if (data == null) { @@ -139,7 +142,7 @@ public final class ECIESAEADEngine { System.arraycopy(data, 0, tag, 0, TAGLEN); RatchetSessionTag st = new RatchetSessionTag(tag); SessionKeyAndNonce key = keyManager.consumeTag(st); - byte decrypted[]; + CloveSet decrypted; final boolean shouldDebug = _log.shouldDebug(); if (key != null) { //if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is known for tag " + st); @@ -150,6 +153,7 @@ public final class ECIESAEADEngine { if (state != null) { decrypted = decryptExistingSession(tag, data, key, targetPrivateKey); } else if (data.length >= MIN_NSR_SIZE) { + /** TODO find the state try { state = state.clone(); } catch (CloneNotSupportedException e) { @@ -158,6 +162,8 @@ public final class ECIESAEADEngine { return null; } decrypted = decryptNewSessionReply(tag, data, state); + **/ + decrypted = null; } else { decrypted = null; if (_log.shouldWarn()) @@ -210,7 +216,7 @@ public final class ECIESAEADEngine { * @param data 96 bytes minimum * @return null if decryption fails */ - private byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey) + private CloveSet decryptNewSession(byte data[], PrivateKey targetPrivateKey) throws DataFormatException { HandshakeState state; try { @@ -271,11 +277,16 @@ public final class ECIESAEADEngine { } catch (Exception e) { throw new DataFormatException("Msg 1 payload error", e); } - if (pc.cloveSet == null) { + if (pc.cloveSet.isEmpty()) { if (_log.shouldWarn()) _log.warn("No garlic block in NS payload"); } - return pc.cloveSet; + int num = pc.cloveSet.size(); + // return non-null even if zero cloves + GarlicClove[] arr = new GarlicClove[num]; + // msg id and expiration not checked in GarlicMessageReceiver + CloveSet rv = new CloveSet(pc.cloveSet.toArray(arr), Certificate.NULL_CERT, 0, pc.datetime); + return rv; } /** @@ -298,7 +309,7 @@ public final class ECIESAEADEngine { * @param state must have already been cloned * @return null if decryption fails */ - private byte[] decryptNewSessionReply(byte[] tag, byte[] data, HandshakeState state) + private CloveSet decryptNewSessionReply(byte[] tag, byte[] data, HandshakeState state) throws DataFormatException { // part 1 - handshake byte[] yy = new byte[KEYLEN]; @@ -361,11 +372,16 @@ public final class ECIESAEADEngine { } RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, new SessionKey(ck), new SessionKey(k_ab), 0, 0); RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, null, new SessionKey(ck), new SessionKey(k_ba), 0, 0, 5, 5); - if (pc.cloveSet == null) { + if (pc.cloveSet.isEmpty()) { if (_log.shouldWarn()) _log.warn("No garlic block in NSR payload"); } - return pc.cloveSet; + int num = pc.cloveSet.size(); + // return non-null even if zero cloves + GarlicClove[] arr = new GarlicClove[num]; + // msg id and expiration not checked in GarlicMessageReceiver + CloveSet rv = new CloveSet(pc.cloveSet.toArray(arr), Certificate.NULL_CERT, 0, pc.datetime); + return rv; } /** @@ -385,7 +401,7 @@ public final class ECIESAEADEngine { * @return decrypted data or null on failure * */ - private byte[] decryptExistingSession(byte[] tag, byte[] data, SessionKeyAndNonce key, PrivateKey targetPrivateKey) + private CloveSet decryptExistingSession(byte[] tag, byte[] data, SessionKeyAndNonce key, PrivateKey targetPrivateKey) throws DataFormatException { // TODO decrypt in place? byte decrypted[] = decryptAEADBlock(tag, data, TAGLEN, data.length - TAGLEN, key, key.getNonce()); @@ -409,11 +425,16 @@ public final class ECIESAEADEngine { } catch (Exception e) { throw new DataFormatException("ES payload error", e); } - if (pc.cloveSet == null) { + if (pc.cloveSet.isEmpty()) { if (_log.shouldWarn()) _log.warn("No garlic block in ES payload"); } - return pc.cloveSet; + int num = pc.cloveSet.size(); + // return non-null even if zero cloves + GarlicClove[] arr = new GarlicClove[num]; + // msg id and expiration not checked in GarlicMessageReceiver + CloveSet rv = new CloveSet(pc.cloveSet.toArray(arr), Certificate.NULL_CERT, 0, pc.datetime); + return rv; } /** @@ -476,12 +497,11 @@ public final class ECIESAEADEngine { * * @param target public key to which the data should be encrypted. * @param priv local private key to encrypt with, from the leaseset - * @param expiration only used for new session messages * @return encrypted data or null on failure * */ - public byte[] encrypt(byte data[], PublicKey target, PrivateKey priv, - RatchetSKM keyManager, long expiration) { + public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, + RatchetSKM keyManager) { if (target.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); if (Arrays.equals(target.getData(), NULLPK)) { @@ -494,7 +514,7 @@ public final class ECIESAEADEngine { if (re == null) { if (_log.shouldDebug()) _log.debug("Encrypting as NS to " + target); - return encryptNewSession(data, target, priv, keyManager, expiration); + return encryptNewSession(cloves, target, priv, keyManager); } //// byte[] tagsetkey = new byte[32]; @@ -513,9 +533,9 @@ public final class ECIESAEADEngine { return null; } // register state with skm - return encryptNewSessionReply(data, state, re.tag); + return encryptNewSessionReply(cloves, state, re.tag); } - byte rv[] = encryptExistingSession(data, target, re.key, re.tag); + byte rv[] = encryptExistingSession(cloves, target, re.key, re.tag); return rv; } @@ -536,8 +556,8 @@ public final class ECIESAEADEngine { * * @return encrypted data or null on failure */ - private byte[] encryptNewSession(byte data[], PublicKey target, PrivateKey priv, - RatchetSKM keyManager, long expiration) { + private byte[] encryptNewSession(CloveSet cloves, PublicKey target, PrivateKey priv, + RatchetSKM keyManager) { HandshakeState state; try { state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread); @@ -549,22 +569,11 @@ public final class ECIESAEADEngine { state.getLocalKeyPair().setPrivateKey(priv.getData(), 0); state.start(); - int padlen = 1 + _context.random().nextInt(MAXPAD); - byte[] payload = new byte[BHLEN + padlen + BHLEN + 4 + BHLEN + data.length]; - List<Block> blocks = new ArrayList<Block>(4); - Block block = new DateTimeBlock(expiration); - blocks.add(block); - block = new GarlicBlock(data); - blocks.add(block); - block = new PaddingBlock(_context, padlen); - blocks.add(block); - int payloadlen = createPayload(payload, 0, blocks); - if (payloadlen != payload.length) - throw new IllegalStateException("payload size mismatch"); + byte[] payload = createPayload(cloves, cloves.getExpiration()); - byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payloadlen + MACLEN]; + byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN]; try { - state.writeMessage(enc, 0, payload, 0, payloadlen); + state.writeMessage(enc, 0, payload, 0, payload.length); } catch (GeneralSecurityException gse) { if (_log.shouldWarn()) _log.warn("Encrypt fail NS", gse); @@ -607,23 +616,14 @@ public final class ECIESAEADEngine { * @param state must have already been cloned * @return encrypted data or null on failure */ - private byte[] encryptNewSessionReply(byte data[], HandshakeState state, RatchetSessionTag currentTag) { + private byte[] encryptNewSessionReply(CloveSet cloves, HandshakeState state, RatchetSessionTag currentTag) { byte[] tag = currentTag.getData(); state.mixHash(tag, 0, TAGLEN); - int padlen = 1 + _context.random().nextInt(MAXPAD); - byte[] payload = new byte[BHLEN + padlen + BHLEN + data.length]; - List<Block> blocks = new ArrayList<Block>(2); - Block block = new GarlicBlock(data); - blocks.add(block); - block = new PaddingBlock(_context, padlen); - blocks.add(block); - int payloadlen = createPayload(payload, 0, blocks); - if (payloadlen != payload.length) - throw new IllegalStateException("payload size mismatch"); + byte[] payload = createPayload(cloves, 0); // part 1 - tag and empty payload - byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payloadlen + MACLEN]; + byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN]; System.arraycopy(tag, 0, enc, 0, TAGLEN); try { state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0); @@ -685,19 +685,10 @@ public final class ECIESAEADEngine { * @param target unused, this is AEAD encrypt only using the session key and tag * @return encrypted data or null on failure */ - private byte[] encryptExistingSession(byte data[], PublicKey target, SessionKeyAndNonce key, + private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, SessionKeyAndNonce key, RatchetSessionTag currentTag) { byte rawTag[] = currentTag.getData(); - int padlen = 1 + _context.random().nextInt(MAXPAD); - byte[] payload = new byte[BHLEN + padlen + BHLEN + data.length]; - List<Block> blocks = new ArrayList<Block>(2); - Block block = new GarlicBlock(data); - blocks.add(block); - block = new PaddingBlock(_context, padlen); - blocks.add(block); - int payloadlen = createPayload(payload, 0, blocks); - if (payloadlen != payload.length) - throw new IllegalStateException("payload size mismatch"); + byte[] payload = createPayload(cloves, 0); byte encr[] = encryptAEADBlock(rawTag, payload, key, key.getNonce()); System.arraycopy(rawTag, 0, encr, 0, TAGLEN); return encr; @@ -741,11 +732,8 @@ public final class ECIESAEADEngine { // payload stuff ///////////////////////////////////////////////////////// - private void processPayload(byte[] payload, int length, boolean isHandshake) throws Exception { - } - private class PLCallback implements RatchetPayload.PayloadCallback { - public byte[] cloveSet; + public final List<GarlicClove> cloveSet = new ArrayList<GarlicClove>(3); public long datetime; public void gotDateTime(long time) { @@ -761,13 +749,10 @@ public final class ECIESAEADEngine { _log.debug("Got OPTIONS block length " + options.length); } - public void gotGarlic(byte[] data, int off, int len) { + public void gotGarlic(GarlicClove clove) { if (_log.shouldDebug()) - _log.debug("Got GARLIC block length " + len); - if (cloveSet != null) - throw new IllegalArgumentException("Multiple GARLIC blocks"); - cloveSet = new byte[len]; - System.arraycopy(data, off, cloveSet, 0, len); + _log.debug("Got GARLIC block"); + cloveSet.add(clove); } public void gotTermination(int reason, long count) { @@ -786,6 +771,38 @@ public final class ECIESAEADEngine { } } + /** + * @param expiration if greater than zero, add a DateTime block + */ + private byte[] createPayload(CloveSet cloves, long expiration) { + int count = cloves.getCloveCount(); + int numblocks = count + 1; + if (expiration > 0) + numblocks++; + int len = 0; + List<Block> blocks = new ArrayList<Block>(numblocks); + if (expiration > 0) { + Block block = new DateTimeBlock(expiration); + blocks.add(block); + len += block.getTotalLength(); + } + for (int i = 0; i < count; i++) { + GarlicClove clove = cloves.getClove(i); + Block block = new GarlicBlock(clove); + blocks.add(block); + len += block.getTotalLength(); + } + int padlen = 1 + _context.random().nextInt(MAXPAD); + Block block = new PaddingBlock(_context, padlen); + blocks.add(block); + len += block.getTotalLength(); + byte[] payload = new byte[len]; + int payloadlen = createPayload(payload, 0, blocks); + if (payloadlen != len) + throw new IllegalStateException("payload size mismatch"); + return payload; + } + /** * @return the new offset */ diff --git a/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java index b15dad7069d744762d04dea69d8c2f7b11253931..a1a1091f010a07ce0d3c65fd37b8715f515ae894 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java @@ -6,6 +6,7 @@ import net.i2p.crypto.EncType; import net.i2p.data.DataFormatException; import net.i2p.data.PrivateKey; import net.i2p.router.RouterContext; +import net.i2p.router.message.CloveSet; import net.i2p.util.Log; /** @@ -28,11 +29,11 @@ public final class MuxedEngine { * * @return decrypted data or null on failure */ - public byte[] decrypt(byte data[], PrivateKey elgKey, PrivateKey ecKey, MuxedSKM keyManager) throws DataFormatException { + public CloveSet decrypt(byte data[], PrivateKey elgKey, PrivateKey ecKey, MuxedSKM keyManager) throws DataFormatException { if (elgKey.getType() != EncType.ELGAMAL_2048 || ecKey.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); - byte[] rv = null; + CloveSet rv = null; boolean tryElg = false; // See proposal 144 if (data.length >= 128) { @@ -41,10 +42,20 @@ public final class MuxedEngine { tryElg = true; } // Always try ElG first, for now - if (tryElg) - rv = _context.elGamalAESEngine().decrypt(data, elgKey, keyManager.getElgSKM()); - if (rv == null) - rv = _context.eciesEngine().decrypt(data, ecKey, keyManager.getECSKM()); + if (tryElg) { + byte[] dec = _context.elGamalAESEngine().decrypt(data, elgKey, keyManager.getElgSKM()); + if (dec != null) { + try { + rv = _context.garlicMessageParser().readCloveSet(dec, 0); + } catch (DataFormatException dfe) { + if (_log.shouldWarn()) + _log.warn("ElG decrypt failed, trying ECIES", dfe); + } + } + } + if (rv == null) { + rv = _context.eciesEngine().decrypt(data, ecKey, keyManager.getECSKM()); + } return rv; } } diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java index 8ddb61f1f158e8367e40f86f3f70fc6bb695caaf..f4c3b25c91bdc4f8819955eb5f41d9c3ab751094 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java @@ -8,6 +8,7 @@ import java.util.List; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.i2np.GarlicClove; import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.I2NPMessageException; @@ -26,13 +27,13 @@ class RatchetPayload { private static final int BLOCK_DATETIME = 0; private static final int BLOCK_SESSIONID = 1; - private static final int BLOCK_GARLIC = 3; private static final int BLOCK_TERMINATION = 4; private static final int BLOCK_OPTIONS = 5; private static final int BLOCK_MSGNUM = 6; private static final int BLOCK_NEXTKEY = 7; private static final int BLOCK_ACKKEY = 8; private static final int BLOCK_REPLYDI = 9; + private static final int BLOCK_GARLIC = 11; private static final int BLOCK_PADDING = 254; /** @@ -43,7 +44,7 @@ class RatchetPayload { public interface PayloadCallback { public void gotDateTime(long time) throws DataFormatException; - public void gotGarlic(byte[] data, int off, int len) throws DataFormatException; + public void gotGarlic(GarlicClove clove); /** * @param isHandshake true only for message 3 part 2 @@ -111,7 +112,9 @@ class RatchetPayload { break; case BLOCK_GARLIC: - cb.gotGarlic(payload, i, len); + GarlicClove clove = new GarlicClove(ctx); + clove.readBytesRatchet(payload, i, len); + cb.gotGarlic(clove); break; case BLOCK_TERMINATION: @@ -200,20 +203,19 @@ class RatchetPayload { } public static class GarlicBlock extends Block { - private byte[] d; + private final GarlicClove c; - public GarlicBlock(byte[] data) { + public GarlicBlock(GarlicClove clove) { super(BLOCK_GARLIC); - d = data; + c = clove; } public int getDataLength() { - return d.length; + return c.getSizeRatchet(); } public int writeData(byte[] tgt, int off) { - System.arraycopy(d, 0, tgt, off, d.length); - return off + d.length; + return c.writeBytesRatchet(tgt, off); } } diff --git a/router/java/src/net/i2p/router/message/CloveSet.java b/router/java/src/net/i2p/router/message/CloveSet.java index 3b9fe3042009dde908bd9db92a477620fdad88cb..19e5c8d6e8d179603a27d22b82c79cc0ba8a5cb7 100644 --- a/router/java/src/net/i2p/router/message/CloveSet.java +++ b/router/java/src/net/i2p/router/message/CloveSet.java @@ -14,8 +14,9 @@ import net.i2p.data.i2np.GarlicClove; /** * Wrap up the data contained in a GarlicMessage after being decrypted * + * @since public since 0.9.44, was package private */ -class CloveSet { +public class CloveSet { private final GarlicClove[] _cloves; private final Certificate _cert; private final long _msgId; diff --git a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java index 33982deab87cdd4f8d6b00146e6cb2007d304815..5321ad7f5d3b4b709e08bdce6cafedd725f72f5d 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java @@ -16,6 +16,7 @@ import java.util.Set; import net.i2p.crypto.EncType; import net.i2p.crypto.SessionKeyManager; +import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; @@ -261,7 +262,7 @@ public class GarlicMessageBuilder { throw new IllegalArgumentException(); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); GarlicMessage msg = new GarlicMessage(ctx); - byte cloveSet[] = buildCloveSet(ctx, config); + CloveSet cloveSet = buildECIESCloveSet(ctx, config); LeaseSetKeys lsk = ctx.keyManager().getKeys(from); if (lsk == null) { if (log.shouldWarn()) @@ -285,7 +286,7 @@ public class GarlicMessageBuilder { log.warn("No SKM for " + from.toBase32()); return null; } - byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, config.getExpiration()); + byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm); if (encData == null) { if (log.shouldWarn()) log.warn("Encrypt fail for " + from.toBase32()); @@ -300,8 +301,8 @@ public class GarlicMessageBuilder { return null; } if (log.shouldDebug()) - log.debug("CloveSet (" + config.getCloveCount() + " cloves) for message " + msg.getUniqueId() + " is " + cloveSet.length - + " bytes and encrypted message data is " + encData.length + " bytes"); + log.debug("CloveSet (" + config.getCloveCount() + " cloves) for message " + msg.getUniqueId() + + " encrypted message data is " + encData.length + " bytes"); return msg; } @@ -366,7 +367,7 @@ public class GarlicMessageBuilder { return baos.toByteArray(); } - private static byte[] buildClove(RouterContext ctx, PayloadGarlicConfig config) throws DataFormatException, IOException { + private static byte[] buildClove(RouterContext ctx, PayloadGarlicConfig config) { GarlicClove clove = new GarlicClove(ctx); clove.setData(config.getPayload()); return buildCommonClove(clove, config); @@ -397,17 +398,52 @@ public class GarlicMessageBuilder { return buildCommonClove(clove, config); } - private static byte[] buildCommonClove(GarlicClove clove, GarlicConfig config) throws DataFormatException, IOException { + private static byte[] buildCommonClove(GarlicClove clove, GarlicConfig config) { clove.setCertificate(config.getCertificate()); clove.setCloveId(config.getId()); clove.setExpiration(new Date(config.getExpiration())); clove.setInstructions(config.getDeliveryInstructions()); return clove.toByteArray(); - /* - int size = clove.estimateSize(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(size); - clove.writeBytes(baos); - return baos.toByteArray(); - */ + } + + /** + * Build the unencrypted GarlicMessage specified by the config. + * It contains the number of cloves, followed by each clove, + * followed by a certificate, ID, and expiration date. + * + * @throws IllegalArgumentException on error + * @since 0.9.44 + */ + private static CloveSet buildECIESCloveSet(RouterContext ctx, GarlicConfig config) { + GarlicClove[] arr; + if (config instanceof PayloadGarlicConfig) { + GarlicClove clove = buildECIESClove(ctx, (PayloadGarlicConfig)config); + arr = new GarlicClove[1]; + arr[0] = clove; + } else { + int cnt = config.getCloveCount(); + arr = new GarlicClove[cnt]; + for (int i = 0; i < cnt; i++) { + GarlicConfig c = config.getClove(i); + if (c instanceof PayloadGarlicConfig) { + arr[i] = buildECIESClove(ctx, (PayloadGarlicConfig)c); + } else { + throw new IllegalArgumentException("Subclove IS NOT a payload garlic clove"); + } + } + } + // GarlicConfig cert, ID, and expiration all ignored here + CloveSet rv = new CloveSet(arr, Certificate.NULL_CERT, config.getId(), config.getExpiration()); + return rv; + } + + private static GarlicClove buildECIESClove(RouterContext ctx, PayloadGarlicConfig config) { + GarlicClove clove = new GarlicClove(ctx); + clove.setData(config.getPayload()); + clove.setCertificate(config.getCertificate()); + clove.setCloveId(config.getId()); + clove.setExpiration(new Date(config.getExpiration())); + clove.setInstructions(config.getDeliveryInstructions()); + return clove; } } diff --git a/router/java/src/net/i2p/router/message/GarlicMessageParser.java b/router/java/src/net/i2p/router/message/GarlicMessageParser.java index 3608fc5b5a1ebe652e82a44c388ad15416dcc669..f4691641ea1eb2b3608495adac2faa610f47f568 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageParser.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageParser.java @@ -70,13 +70,15 @@ public class GarlicMessageParser { _log.warn("No SKM to decrypt ECIES"); return null; } - decrData = _context.eciesEngine().decrypt(encData, encryptionKey, rskm); - if (decrData != null) { + CloveSet rv = _context.eciesEngine().decrypt(encData, encryptionKey, rskm); + if (rv != null) { if (_log.shouldWarn()) - _log.warn("ECIES decrypt success, length: " + decrData.length); + _log.warn("ECIES decrypt success, cloves: " + rv.getCloveCount()); + return rv; } else { if (_log.shouldWarn()) _log.warn("ECIES decrypt fail"); + return null; } } else { if (_log.shouldWarn()) @@ -108,10 +110,13 @@ public class GarlicMessageParser { } /** + * ElGamal only + * * @param offset where in data to start * @return non-null, throws on all errors + * @since public since 0.9.44 */ - private CloveSet readCloveSet(byte data[], int offset) throws DataFormatException { + public CloveSet readCloveSet(byte data[], int offset) throws DataFormatException { int numCloves = data[offset] & 0xff; offset++; //if (_log.shouldLog(Log.DEBUG))