forked from I2P_Developers/i2p.i2p
Ratchet: Changes to match current proposal 144
Pass CloveSet to/from ECIESEngine
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user