OCMOSJ: Select target key in LS based on local client's support

Hook new SKMs and ending into Garlic Message encryption/decryption
Remove unused wrappedKey param from buildMessage()
Log tweaks and javadoc fixes
WIP
This commit is contained in:
zzz
2019-10-25 12:21:33 +00:00
parent 43c93bceed
commit 0c256d30c7
6 changed files with 218 additions and 59 deletions

View File

@ -14,17 +14,22 @@ import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import net.i2p.crypto.EncType;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey; import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag; import net.i2p.data.SessionTag;
import net.i2p.data.i2np.GarlicClove; import net.i2p.data.i2np.GarlicClove;
import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.LeaseSetKeys;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -34,10 +39,14 @@ import net.i2p.util.Log;
public class GarlicMessageBuilder { public class GarlicMessageBuilder {
/** /**
* ELGAMAL_2048 only.
*
* @param local non-null; do not use this method for the router's SessionKeyManager * @param local non-null; do not use this method for the router's SessionKeyManager
* @param minTagOverride 0 for no override, > 0 to override SKM's settings * @param minTagOverride 0 for no override, > 0 to override SKM's settings
*/ */
static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) { static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) {
if (key.getType() == EncType.ECIES_X25519)
return false;
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local); SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
if (skm == null) if (skm == null)
return true; return true;
@ -51,6 +60,7 @@ public class GarlicMessageBuilder {
/** /**
* Unused and probably a bad idea. * Unused and probably a bad idea.
* ELGAMAL_2048 only.
* *
* Used below only on a recursive call if the garlic message contains a garlic message. * Used below only on a recursive call if the garlic message contains a garlic message.
* We don't need the SessionKey or SesssionTags returned * We don't need the SessionKey or SesssionTags returned
@ -60,6 +70,7 @@ public class GarlicMessageBuilder {
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @return null if expired
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
private static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config) { private static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config) {
@ -70,14 +81,17 @@ public class GarlicMessageBuilder {
/** /**
* Now unused, since we have to generate a reply token first in OCMOSJ but we don't know if tags are required yet. * Now unused, since we have to generate a reply token first in OCMOSJ but we don't know if tags are required yet.
* ELGAMAL_2048 only.
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used * @param wrappedKey non-null with null data,
* output parameter that will be filled with the SessionKey used
* @param wrappedTags Output parameter that will be filled with the sessionTags used. * @param wrappedTags Output parameter that will be filled with the sessionTags used.
If non-empty on return you must call skm.tagsDelivered() when sent If non-empty on return you must call skm.tagsDelivered() when sent
and then call skm.tagsAcked() or skm.failTags() later. and then call skm.tagsAcked() or skm.failTags() later.
* @param skm non-null * @param skm non-null
* @return null if expired
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags, public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
@ -94,11 +108,13 @@ public class GarlicMessageBuilder {
***/ ***/
/** /**
* called by OCMJH * ELGAMAL_2048 only.
* Called by OCMJH.
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used * @param wrappedKey non-null with null data,
* output parameter that will be filled with the SessionKey used
* @param wrappedTags Output parameter that will be filled with the sessionTags used. * @param wrappedTags Output parameter that will be filled with the sessionTags used.
If non-empty on return you must call skm.tagsDelivered() when sent If non-empty on return you must call skm.tagsDelivered() when sent
and then call skm.tagsAcked() or skm.failTags() later. and then call skm.tagsAcked() or skm.failTags() later.
@ -106,6 +122,7 @@ public class GarlicMessageBuilder {
Set to zero to disable tag delivery. You must set to zero if you are not Set to zero to disable tag delivery. You must set to zero if you are not
equipped to confirm delivery and call skm.tagsAcked() or skm.failTags() later. equipped to confirm delivery and call skm.tagsAcked() or skm.failTags() later.
* @param skm non-null * @param skm non-null
* @return null if expired
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags, public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
@ -114,11 +131,13 @@ public class GarlicMessageBuilder {
} }
/** /**
* called by netdb and above * ELGAMAL_2048 only.
* Called by netdb and above.
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used * @param wrappedKey non-null with null data,
* output parameter that will be filled with the SessionKey used
* @param wrappedTags Output parameter that will be filled with the sessionTags used. * @param wrappedTags Output parameter that will be filled with the sessionTags used.
If non-empty on return you must call skm.tagsDelivered() when sent If non-empty on return you must call skm.tagsDelivered() when sent
and then call skm.tagsAcked() or skm.failTags() later. and then call skm.tagsAcked() or skm.failTags() later.
@ -128,6 +147,7 @@ public class GarlicMessageBuilder {
If this is always 0, it forces ElGamal every time. If this is always 0, it forces ElGamal every time.
* @param lowTagsThreshold the threshold * @param lowTagsThreshold the threshold
* @param skm non-null * @param skm non-null
* @return null if expired
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags, public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
@ -144,14 +164,14 @@ public class GarlicMessageBuilder {
} else } else
key = config.getRecipient().getIdentity().getPublicKey(); key = config.getRecipient().getIdentity().getPublicKey();
} }
if (key.getType() != EncType.ELGAMAL_2048)
throw new IllegalArgumentException();
if (log.shouldLog(Log.INFO)) if (log.shouldLog(Log.INFO))
log.info("Encrypted with public key to expire on " + new Date(config.getExpiration())); log.info("Encrypted with public key to expire on " + new Date(config.getExpiration()));
SessionKey curKey = skm.getCurrentOrNewKey(key); SessionKey curKey = skm.getCurrentOrNewKey(key);
SessionTag curTag = null; SessionTag curTag = skm.consumeNextAvailableTag(key, curKey);
curTag = skm.consumeNextAvailableTag(key, curKey);
if (log.shouldLog(Log.DEBUG)) { if (log.shouldLog(Log.DEBUG)) {
int availTags = skm.getAvailableTags(key, curKey); int availTags = skm.getAvailableTags(key, curKey);
@ -167,25 +187,26 @@ public class GarlicMessageBuilder {
wrappedKey.setData(curKey.getData()); wrappedKey.setData(curKey.getData());
return buildMessage(ctx, config, wrappedKey, wrappedTags, key, curKey, curTag); return buildMessage(ctx, config, wrappedTags, key, curKey, curTag);
} }
/** /**
* used by TestJob and directly above * ELGAMAL_2048 only.
* and for encrypting DatabaseLookupMessages * Used by TestJob, and directly above,
* and by MessageWrapper for encrypting DatabaseLookupMessages
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param wrappedKey unused - why??
* @param wrappedTags New tags to be sent along with the message. * @param wrappedTags New tags to be sent along with the message.
* 200 max enforced at receiver; null OK * 200 max enforced at receiver; null OK
* @param target public key of the location being garlic routed to (may be null if we * @param target public key of the location being garlic routed to (may be null if we
* know the encryptKey and encryptTag) * know the encryptKey and encryptTag)
* @param encryptKey sessionKey used to encrypt the current message, non-null * @param encryptKey sessionKey used to encrypt the current message, non-null
* @param encryptTag sessionTag used to encrypt the current message, null to force ElG * @param encryptTag sessionTag used to encrypt the current message, null to force ElG
* @return null if expired
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags, public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, Set<SessionTag> wrappedTags,
PublicKey target, SessionKey encryptKey, SessionTag encryptTag) { PublicKey target, SessionKey encryptKey, SessionTag encryptTag) {
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
if (config == null) if (config == null)
@ -199,6 +220,11 @@ public class GarlicMessageBuilder {
// TODO - 128 is the minimum padded size - should it be more? less? random? // TODO - 128 is the minimum padded size - should it be more? less? random?
byte encData[] = ctx.elGamalAESEngine().encrypt(cloveSet, target, encryptKey, wrappedTags, encryptTag, 128); byte encData[] = ctx.elGamalAESEngine().encrypt(cloveSet, target, encryptKey, wrappedTags, encryptTag, 128);
if (encData == null) {
if (log.shouldWarn())
log.warn("ElG encrypt fail");
return null;
}
msg.setData(encData); msg.setData(encData);
msg.setMessageExpiration(config.getExpiration()); msg.setMessageExpiration(config.getExpiration());
@ -216,6 +242,69 @@ public class GarlicMessageBuilder {
return msg; return msg;
} }
/**
* ECIES_X25519 only.
* Called by GarlicMessageBuilder only.
*
* @param ctx scope
* @param config how/what to wrap
* @param target public key of the location being garlic routed to (may be null if we
* know the encryptKey and encryptTag)
* @return null if expired or on other errors
* @throws IllegalArgumentException on error
* @since 0.9.44
*/
static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config,
PublicKey target, Hash from, SessionKeyManager skm) {
PublicKey key = config.getRecipientPublicKey();
if (key.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException();
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
GarlicMessage msg = new GarlicMessage(ctx);
byte cloveSet[] = buildCloveSet(ctx, config);
LeaseSetKeys lsk = ctx.keyManager().getKeys(from);
if (lsk == null) {
if (log.shouldWarn())
log.warn("No LSK for " + from.toBase32());
return null;
}
PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519);
if (priv == null) {
if (log.shouldWarn())
log.warn("No key for " + from.toBase32());
return null;
}
RatchetSKM rskm;
if (skm instanceof RatchetSKM) {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else {
if (log.shouldWarn())
log.warn("No SKM for " + from.toBase32());
return null;
}
byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, config.getExpiration());
if (encData == null) {
if (log.shouldWarn())
log.warn("Encrypt fail for " + from.toBase32());
return null;
}
msg.setData(encData);
msg.setMessageExpiration(config.getExpiration());
long timeFromNow = config.getExpiration() - ctx.clock().now();
if (timeFromNow < 1*1000) {
if (log.shouldDebug())
log.debug("Building a message expiring in " + timeFromNow + "ms: " + config, new Exception("created by"));
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");
return msg;
}
/**** /****
private static void noteWrap(RouterContext ctx, GarlicMessage wrapper, GarlicConfig contained) { private static void noteWrap(RouterContext ctx, GarlicMessage wrapper, GarlicConfig contained) {
for (int i = 0; i < contained.getCloveCount(); i++) { for (int i = 0; i < contained.getCloveCount(); i++) {
@ -237,8 +326,7 @@ public class GarlicMessageBuilder {
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
*/ */
private static byte[] buildCloveSet(RouterContext ctx, GarlicConfig config) { private static byte[] buildCloveSet(RouterContext ctx, GarlicConfig config) {
ByteArrayOutputStream baos = null; ByteArrayOutputStream baos;
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
try { try {
if (config instanceof PayloadGarlicConfig) { if (config instanceof PayloadGarlicConfig) {
byte clove[] = buildClove(ctx, (PayloadGarlicConfig)config); byte clove[] = buildClove(ctx, (PayloadGarlicConfig)config);
@ -253,7 +341,7 @@ public class GarlicMessageBuilder {
//log.debug("Subclove IS a payload garlic clove"); //log.debug("Subclove IS a payload garlic clove");
cloves[i] = buildClove(ctx, (PayloadGarlicConfig)c); cloves[i] = buildClove(ctx, (PayloadGarlicConfig)c);
} else { } else {
log.debug("Subclove IS NOT a payload garlic clove"); //log.debug("Subclove IS NOT a payload garlic clove");
// See notes below // See notes below
cloves[i] = buildClove(ctx, c); cloves[i] = buildClove(ctx, c);
} }
@ -271,10 +359,8 @@ public class GarlicMessageBuilder {
DataHelper.writeLong(baos, 4, config.getId()); DataHelper.writeLong(baos, 4, config.getId());
DataHelper.writeLong(baos, DataHelper.DATE_LENGTH, config.getExpiration()); DataHelper.writeLong(baos, DataHelper.DATE_LENGTH, config.getExpiration());
} catch (IOException ioe) { } catch (IOException ioe) {
log.error("Error building the clove set", ioe);
throw new IllegalArgumentException("Error building the clove set", ioe); throw new IllegalArgumentException("Error building the clove set", ioe);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
log.error("Error building the clove set", dfe);
throw new IllegalArgumentException("Error building the clove set", dfe); throw new IllegalArgumentException("Error building the clove set", dfe);
} }
return baos.toByteArray(); return baos.toByteArray();

View File

@ -10,6 +10,7 @@ package net.i2p.router.message;
import java.util.Date; import java.util.Date;
import net.i2p.crypto.EncType;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
@ -18,6 +19,8 @@ import net.i2p.data.PrivateKey;
import net.i2p.data.i2np.GarlicClove; import net.i2p.data.i2np.GarlicClove;
import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -41,16 +44,45 @@ public class GarlicMessageParser {
} }
/** /**
* Supports both ELGAMAL_2048 and ECIES_X25519.
*
* @param encryptionKey either type TODO need both for muxed
* @param skm use tags from this session key manager * @param skm use tags from this session key manager
* @return null on error * @return null on error
*/ */
public CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey, SessionKeyManager skm) { CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey, SessionKeyManager skm) {
byte encData[] = message.getData(); byte encData[] = message.getData();
byte decrData[]; byte decrData[];
try { try {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypting with private key " + encryptionKey); _log.debug("Decrypting with private key " + encryptionKey);
decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey, skm); EncType type = encryptionKey.getType();
if (type == EncType.ELGAMAL_2048) {
decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey, skm);
} else if (type == EncType.ECIES_X25519) {
RatchetSKM rskm;
if (skm instanceof RatchetSKM) {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else {
if (_log.shouldWarn())
_log.warn("No SKM to decrypt ECIES");
return null;
}
decrData = _context.eciesEngine().decrypt(encData, encryptionKey, rskm);
if (decrData != null) {
if (_log.shouldWarn())
_log.warn("ECIES decrypt success, length: " + decrData.length);
} else {
if (_log.shouldWarn())
_log.warn("ECIES decrypt fail");
}
} else {
if (_log.shouldWarn())
_log.warn("Can't decrypt with key type " + type);
return null;
}
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Error decrypting", dfe); _log.warn("Error decrypting", dfe);
@ -63,7 +95,10 @@ public class GarlicMessageParser {
return null; return null;
} else { } else {
try { try {
return readCloveSet(decrData); CloveSet rv = readCloveSet(decrData, 0);
if (_log.shouldDebug())
_log.debug("Got cloves: " + rv.getCloveCount());
return rv;
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Unable to read cloveSet", dfe); _log.warn("Unable to read cloveSet", dfe);
@ -72,13 +107,15 @@ public class GarlicMessageParser {
} }
} }
private CloveSet readCloveSet(byte data[]) throws DataFormatException { /**
int offset = 0; * @param offset where in data to start
* @return non-null, throws on all errors
*/
private CloveSet readCloveSet(byte data[], int offset) throws DataFormatException {
int numCloves = data[offset] & 0xff; int numCloves = data[offset] & 0xff;
offset++; offset++;
if (_log.shouldLog(Log.DEBUG)) //if (_log.shouldLog(Log.DEBUG))
_log.debug("# cloves to read: " + numCloves); // _log.debug("# cloves to read: " + numCloves);
if (numCloves <= 0 || numCloves > MAX_CLOVES) if (numCloves <= 0 || numCloves > MAX_CLOVES)
throw new DataFormatException("bad clove count " + numCloves); throw new DataFormatException("bad clove count " + numCloves);
GarlicClove[] cloves = new GarlicClove[numCloves]; GarlicClove[] cloves = new GarlicClove[numCloves];

View File

@ -8,6 +8,7 @@ package net.i2p.router.message;
* *
*/ */
import net.i2p.crypto.EncType;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Hash; import net.i2p.data.Hash;
@ -21,7 +22,7 @@ import net.i2p.router.RouterContext;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
* Unencrypt a garlic message and pass off any valid cloves to the configured * Decrypt a garlic message and pass off any valid cloves to the configured
* receiver to dispatch as they choose. * receiver to dispatch as they choose.
* *
*/ */
@ -61,7 +62,16 @@ public class GarlicMessageReceiver {
LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination); LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination);
skm = _context.clientManager().getClientSessionKeyManager(_clientDestination); skm = _context.clientManager().getClientSessionKeyManager(_clientDestination);
if (keys != null && skm != null) { if (keys != null && skm != null) {
// TODO need to pass both keys if available for muxed decrypt
decryptionKey = keys.getDecryptionKey(); decryptionKey = keys.getDecryptionKey();
if (decryptionKey == null) {
decryptionKey = keys.getDecryptionKey(EncType.ECIES_X25519);
if (decryptionKey == null) {
if (_log.shouldWarn())
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
return;
}
}
} else { } else {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Not trying to decrypt a garlic routed message to a disconnected client"); _log.warn("Not trying to decrypt a garlic routed message to a disconnected client");
@ -72,6 +82,7 @@ public class GarlicMessageReceiver {
skm = _context.sessionKeyManager(); skm = _context.sessionKeyManager();
} }
// TODO need to pass both keys if available for muxed decrypt
CloveSet set = _context.garlicMessageParser().getGarlicCloves(message, decryptionKey, skm); CloveSet set = _context.garlicMessageParser().getGarlicCloves(message, decryptionKey, skm);
if (set != null) { if (set != null) {
for (int i = 0; i < set.getCloveCount(); i++) { for (int i = 0; i < set.getCloveCount(); i++) {
@ -79,9 +90,13 @@ public class GarlicMessageReceiver {
handleClove(clove); handleClove(clove);
} }
} else { } else {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN)) {
_log.warn("CloveMessageParser failed to decrypt the message [" + message.getUniqueId() String d = (_clientDestination != null) ? _clientDestination.toBase32() : "the router";
+ "]", new Exception("Decrypt garlic failed")); _log.warn("CloveMessageParser failed to decrypt the " + message.getData().length +
" byte message [" + message.getUniqueId()
+ "] for " + d + " with key " + decryptionKey.getType(),
new Exception("Decrypt garlic failed"));
}
_context.statManager().addRateData("crypto.garlic.decryptFail", 1); _context.statManager().addRateData("crypto.garlic.decryptFail", 1);
_context.messageHistory().messageProcessingError(message.getUniqueId(), _context.messageHistory().messageProcessingError(message.getUniqueId(),
message.getClass().getName(), message.getClass().getName(),
@ -110,10 +125,10 @@ public class GarlicMessageReceiver {
boolean rv = invalidReason == null; boolean rv = invalidReason == null;
if (!rv) { if (!rv) {
String howLongAgo = DataHelper.formatDuration(_context.clock().now()-clove.getExpiration().getTime()); String howLongAgo = DataHelper.formatDuration(_context.clock().now()-clove.getExpiration().getTime());
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldInfo())
_log.debug("Clove is NOT valid: id=" + clove.getCloveId() _log.info("Clove is NOT valid: id=" + clove.getCloveId()
+ " expiration " + howLongAgo + " ago", new Exception("Invalid within...")); + " expiration " + howLongAgo + " ago", new Exception("Invalid within..."));
else if (_log.shouldLog(Log.WARN)) else if (_log.shouldWarn())
_log.warn("Clove is NOT valid: id=" + clove.getCloveId() _log.warn("Clove is NOT valid: id=" + clove.getCloveId()
+ " expiration " + howLongAgo + " ago: " + invalidReason + ": " + clove); + " expiration " + howLongAgo + " ago: " + invalidReason + ": " + clove);
_context.messageHistory().messageProcessingError(clove.getCloveId(), _context.messageHistory().messageProcessingError(clove.getCloveId(),

View File

@ -10,6 +10,7 @@ package net.i2p.router.message;
import java.util.Set; import java.util.Set;
import net.i2p.crypto.EncType;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.Destination; import net.i2p.data.Destination;
@ -72,9 +73,8 @@ class OutboundClientMessageJobHelper {
* *
* For now, its just a tunneled DeliveryStatusMessage * For now, its just a tunneled DeliveryStatusMessage
* *
* Unused? * @param wrappedKey non-null with null data,
* * output parameter that will be filled with the SessionKey used
* @param wrappedKey output parameter that will be filled with the sessionKey used
* @param wrappedTags output parameter that will be filled with the sessionTags used * @param wrappedTags output parameter that will be filled with the sessionTags used
* @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing * @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing
* much faster replies, since their netDb search will return almost instantly) * much faster replies, since their netDb search will return almost instantly)
@ -101,7 +101,8 @@ class OutboundClientMessageJobHelper {
* *
* @param tagsToSendOverride if &gt; 0, use this instead of skm's default * @param tagsToSendOverride if &gt; 0, use this instead of skm's default
* @param lowTagsOverride if &gt; 0, use this instead of skm's default * @param lowTagsOverride if &gt; 0, use this instead of skm's default
* @param wrappedKey output parameter that will be filled with the sessionKey used * @param wrappedKey non-null with null data,
* output parameter that will be filled with the SessionKey used
* @param wrappedTags output parameter that will be filled with the sessionTags used * @param wrappedTags output parameter that will be filled with the sessionTags used
* @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null * @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null
* @param requireAck if true, bundle replyToken in an ack clove * @param requireAck if true, bundle replyToken in an ack clove
@ -120,11 +121,16 @@ class OutboundClientMessageJobHelper {
from, dest, replyTunnel, requireAck, bundledReplyLeaseSet, skm); from, dest, replyTunnel, requireAck, bundledReplyLeaseSet, skm);
if (config == null) if (config == null)
return null; return null;
// no use sending tags unless we have a reply token set up already GarlicMessage msg;
int tagsToSend = replyToken >= 0 ? (tagsToSendOverride > 0 ? tagsToSendOverride : skm.getTagsToSend()) : 0; if (recipientPK.getType() == EncType.ECIES_X25519) {
int lowThreshold = lowTagsOverride > 0 ? lowTagsOverride : skm.getLowThreshold(); msg = GarlicMessageBuilder.buildECIESMessage(ctx, config, recipientPK, from, skm);
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags, } else {
tagsToSend, lowThreshold, skm); // no use sending tags unless we have a reply token set up already
int tagsToSend = replyToken >= 0 ? (tagsToSendOverride > 0 ? tagsToSendOverride : skm.getTagsToSend()) : 0;
int lowThreshold = lowTagsOverride > 0 ? lowTagsOverride : skm.getLowThreshold();
msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags,
tagsToSend, lowThreshold, skm);
}
return msg; return msg;
} }
@ -150,7 +156,8 @@ class OutboundClientMessageJobHelper {
ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE), ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE),
expiration, DeliveryInstructions.LOCAL); expiration, DeliveryInstructions.LOCAL);
if (requireAck) { // for now, skip this for ratchet
if (requireAck && recipientPK.getType() == EncType.ELGAMAL_2048) {
// extend the expiration of the return message // extend the expiration of the return message
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken, PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken,
expiration + ACK_EXTRA_EXPIRATION, skm); expiration + ACK_EXTRA_EXPIRATION, skm);

View File

@ -32,6 +32,7 @@ import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.ClientMessage; import net.i2p.router.ClientMessage;
import net.i2p.router.JobImpl; import net.i2p.router.JobImpl;
import net.i2p.router.LeaseSetKeys;
import net.i2p.router.MessageSelector; import net.i2p.router.MessageSelector;
import net.i2p.router.ReplyJob; import net.i2p.router.ReplyJob;
import net.i2p.router.Router; import net.i2p.router.Router;
@ -116,6 +117,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
private LeaseSet _leaseSet; private LeaseSet _leaseSet;
/** Actual lease the message is being routed through */ /** Actual lease the message is being routed through */
private Lease _lease; private Lease _lease;
/** Actual target encryption key from the LS being used */
private PublicKey _encryptionKey;
private final long _start; private final long _start;
/** note we can succeed after failure, but not vice versa */ /** note we can succeed after failure, but not vice versa */
private enum Result {NONE, FAIL, SUCCESS} private enum Result {NONE, FAIL, SUCCESS}
@ -373,8 +376,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
} }
/** /**
* Choose a lease from his leaseset to send the message to. Sets _lease. * Choose a lease from his leaseset to send the message to.
*
* Side effects:
* Sets _lease.
* Sets _wantACK if it's new or changed. * Sets _wantACK if it's new or changed.
* Sets _encryptionKey.
*
* Does several checks to see if we can actually send to this leaseset, * Does several checks to see if we can actually send to this leaseset,
* and returns nonzero failure code if unable to. * and returns nonzero failure code if unable to.
* *
@ -399,12 +407,21 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
lsType != DatabaseEntry.KEY_TYPE_LS2) { lsType != DatabaseEntry.KEY_TYPE_LS2) {
return MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET; return MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET;
} }
PublicKey pk = _leaseSet.getEncryptionKey();
if (pk == null) // select an encryption key from the leaseset
Set<EncType> supported;
LeaseSetKeys ourKeys = getContext().keyManager().getKeys(_from);
if (ourKeys != null)
supported = ourKeys.getSupportedEncryption();
else
supported = LeaseSetKeys.SET_ELG;
_encryptionKey = _leaseSet.getEncryptionKey(supported);
if (_encryptionKey == null) {
if (_leaseSet.getEncryptionKey() != null)
return MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION;
// no keys at all?
return MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET; return MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET;
EncType encType = pk.getType(); }
if (encType == null || !encType.isAvailable())
return MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION;
// Use the same lease if it's still good // Use the same lease if it's still good
// Even if _leaseSet changed, _leaseSet.getEncryptionKey() didn't... // Even if _leaseSet changed, _leaseSet.getEncryptionKey() didn't...
@ -590,7 +607,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
int tagsRequired = SendMessageOptions.getTagThreshold(sendFlags); int tagsRequired = SendMessageOptions.getTagThreshold(sendFlags);
boolean wantACK = _wantACK || boolean wantACK = _wantACK ||
shouldRequestReply || shouldRequestReply ||
GarlicMessageBuilder.needsTags(getContext(), _leaseSet.getEncryptionKey(), GarlicMessageBuilder.needsTags(getContext(), _encryptionKey,
_from.calculateHash(), tagsRequired); _from.calculateHash(), tagsRequired);
LeaseSet replyLeaseSet; LeaseSet replyLeaseSet;
@ -627,17 +644,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION); dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION);
return; return;
} }
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(getJobId() + ": Clove built to " + _toString);
PublicKey key = _leaseSet.getEncryptionKey();
SessionKey sessKey = new SessionKey(); SessionKey sessKey = new SessionKey();
Set<SessionTag> tags = new HashSet<SessionTag>(); Set<SessionTag> tags = new HashSet<SessionTag>();
// Per-message flag > 0 overrides per-session option // Per-message flag > 0 overrides per-session option
int tagsToSend = SendMessageOptions.getTagsToSend(sendFlags); int tagsToSend = SendMessageOptions.getTagsToSend(sendFlags);
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token, GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
_overallExpiration, key, _overallExpiration, _encryptionKey,
clove, _from.calculateHash(), clove, _from.calculateHash(),
_to, _inTunnel, tagsToSend, _to, _inTunnel, tagsToSend,
tagsRequired, sessKey, tags, tagsRequired, sessKey, tags,
@ -664,7 +678,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
if (!tags.isEmpty()) { if (!tags.isEmpty()) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash()); SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null) if (skm != null)
tsh = skm.tagsDelivered(_leaseSet.getEncryptionKey(), sessKey, tags); tsh = skm.tagsDelivered(_encryptionKey, sessKey, tags);
} }
onReply = new SendSuccessJob(getContext(), sessKey, tsh); onReply = new SendSuccessJob(getContext(), sessKey, tsh);
onFail = new SendTimeoutJob(getContext(), sessKey, tsh); onFail = new SendTimeoutJob(getContext(), sessKey, tsh);
@ -1019,7 +1033,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
if (_key != null && _tags != null && _leaseSet != null) { if (_key != null && _tags != null && _leaseSet != null) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash()); SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null) if (skm != null)
skm.tagsAcked(_leaseSet.getEncryptionKey(), _key, _tags); skm.tagsAcked(_encryptionKey, _key, _tags);
} }
} }
@ -1110,7 +1124,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
if (_key != null && _tags != null && _leaseSet != null) { if (_key != null && _tags != null && _leaseSet != null) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash()); SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null) if (skm != null)
skm.failTags(_leaseSet.getEncryptionKey(), _key, _tags); skm.failTags(_encryptionKey, _key, _tags);
} }
} }
if (old == Result.NONE) if (old == Result.NONE)

View File

@ -139,7 +139,7 @@ public class MessageWrapper {
payload.setRecipient(to); payload.setRecipient(to);
SessionKey sentKey = ctx.keyGenerator().generateSessionKey(); SessionKey sentKey = ctx.keyGenerator().generateSessionKey();
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null, null, GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null,
key, sentKey, null); key, sentKey, null);
return msg; return msg;
} }
@ -234,7 +234,7 @@ public class MessageWrapper {
m.getMessageExpiration(), m.getMessageExpiration(),
DeliveryInstructions.LOCAL, m); DeliveryInstructions.LOCAL, m);
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null, null, GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null,
null, encryptKey, encryptTag); null, encryptKey, encryptTag);
return msg; return msg;
} }