* Router: Encrypt DeliveryStatusMessages sent in garlics (ticket #1217)

This commit is contained in:
zzz
2014-03-06 16:21:36 +00:00
parent 8c820bb237
commit a0724dc009
4 changed files with 119 additions and 24 deletions

View File

@@ -1,3 +1,6 @@
2014-03-06 zzz
* Router: Encrypt DeliveryStatusMessages sent in garlics (ticket #1217)
2014-03-05 zzz
* Transports: Don't send a duplicate store of our RI at
start of a connection (ticket #1187)

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 13;
public final static long BUILD = 14;
/** for example "-test" */
public final static String EXTRA = "";

View File

@@ -28,13 +28,37 @@ import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.router.networkdb.kademlia.MessageWrapper;
import net.i2p.util.Log;
/**
* Handle a particular client message that is destined for a remote destination.
* Static methods to create a Garlic Message with one or more cloves, as follows:
*
* <pre>
*
* GarlicMessage
* Ack Clove (optional)
* Tunnel delivery instructions
* Garlic Message
* Delivery Status Clove
* Local delivery instructions
* Delivery Status Message
* LeaseSet Clove (optional)
* Local delivery instructions
* Database Store Message
* Data Clove (required)
* Destination delivery instructions
* Data Message
*
* </pre>
*
* The low-level construction is in GarlicMessageBuilder.
*
*/
class OutboundClientMessageJobHelper {
private static final long ACK_EXTRA_EXPIRATION = 60*1000;
/**
* Build a garlic message that will be delivered to the router on which the target is located.
* Inside the message are two cloves: one containing the payload with instructions for
@@ -54,8 +78,11 @@ class OutboundClientMessageJobHelper {
* @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
* much faster replies, since their netDb search will return almost instantly)
* @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null
* @param requireAck if true, bundle replyToken in an ack clove
* @return garlic, or null if no tunnels were found (or other errors)
*/
/****
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
Payload data, Hash from, Destination dest, TunnelInfo replyTunnel,
SessionKey wrappedKey, Set<SessionTag> wrappedTags,
@@ -64,6 +91,8 @@ class OutboundClientMessageJobHelper {
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel,
0, 0, wrappedKey, wrappedTags, requireAck, bundledReplyLeaseSet);
}
****/
/**
* Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the
* same payload (including expiration and unique id) in different garlics (down different tunnels)
@@ -74,18 +103,23 @@ class OutboundClientMessageJobHelper {
* @param lowTagsOverride if > 0, use this instead of skm's default
* @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 replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null
* @param requireAck if true, bundle replyToken in an ack clove
* @param bundledReplyLeaseSet may be null; if non-null, put it in a clove
* @return garlic, or null if no tunnels were found (or other errors)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel,
int tagsToSendOverride, int lowTagsOverride, SessionKey wrappedKey,
Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, requireAck, bundledReplyLeaseSet);
if (config == null)
return null;
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
if (skm == null)
return null;
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,
from, dest, replyTunnel, requireAck, bundledReplyLeaseSet, skm);
if (config == null)
return null;
// 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();
@@ -95,20 +129,31 @@ class OutboundClientMessageJobHelper {
}
/**
* Make the top-level config, with a data clove, an optional ack clove, and
* an optional leaseset clove.
*
* @param dataClove 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 bundledReplyLeaseSet may be null; if non-null, put it in a clove
* @param skm encrypt dsm with this skm non-null
* @return null on error
*/
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, boolean requireAck,
LeaseSet bundledReplyLeaseSet) {
PayloadGarlicConfig dataClove, Hash from, Destination dest,
TunnelInfo replyTunnel, boolean requireAck,
LeaseSet bundledReplyLeaseSet, SessionKeyManager skm) {
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
if (replyToken >= 0 && log.shouldLog(Log.DEBUG))
log.debug("Reply token: " + replyToken);
GarlicConfig config = new GarlicConfig();
if (requireAck) {
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken, expiration);
// extend the expiration of the return message
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken,
expiration + ACK_EXTRA_EXPIRATION, skm);
if (ackClove == null)
return null; // no tunnels
return null; // no tunnels... TODO carry on anyway?
config.addClove(ackClove);
}
@@ -136,12 +181,15 @@ class OutboundClientMessageJobHelper {
}
/**
* Build a clove that sends a DeliveryStatusMessage to us
* @return null on error
* Build a clove that sends a DeliveryStatusMessage to us.
* As of 0.9.12, the DSM is wrapped in a GarlicMessage.
* @param skm encrypt dsm with this skm non-null
* @return null on error
*/
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, TunnelInfo replyToTunnel, long replyToken, long expiration) {
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, TunnelInfo replyToTunnel,
long replyToken, long expiration,
SessionKeyManager skm) {
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
if (replyToTunnel == null) {
if (log.shouldLog(Log.WARN))
@@ -149,6 +197,7 @@ class OutboundClientMessageJobHelper {
+ ", as there are no inbound tunnels available");
return null;
}
TunnelId replyToTunnelId = replyToTunnel.getReceiveTunnelId(0); // tunnel id on that gateway
Hash replyToTunnelRouter = replyToTunnel.getPeer(0); // inbound tunnel gateway
if (log.shouldLog(Log.DEBUG))
@@ -164,18 +213,16 @@ class OutboundClientMessageJobHelper {
//ackInstructions.setDelaySeconds(0);
//ackInstructions.setEncrypted(false);
DeliveryStatusMessage msg = new DeliveryStatusMessage(ctx);
msg.setArrival(ctx.clock().now());
msg.setMessageId(replyToken);
//if (log.shouldLog(Log.DEBUG))
// log.debug("Delivery status message key: " + replyToken + " arrival: " + msg.getArrival());
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
ackClove.setCertificate(Certificate.NULL_CERT);
ackClove.setDeliveryInstructions(ackInstructions);
ackClove.setExpiration(expiration);
ackClove.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE));
DeliveryStatusMessage dsm = buildDSM(ctx, replyToken);
GarlicMessage msg = wrapDSM(ctx, skm, dsm);
ackClove.setPayload(msg);
ackClove.setRecipient(ctx.router().getRouterInfo());
// this does nothing, the clove is not separately encrypted
//ackClove.setRecipient(ctx.router().getRouterInfo());
// defaults
//ackClove.setRequestAck(false);
@@ -188,11 +235,41 @@ class OutboundClientMessageJobHelper {
return ackClove;
}
/**
* Make a basic DSM
* @since 0.9.12
*/
private static DeliveryStatusMessage buildDSM(RouterContext ctx, long replyToken) {
DeliveryStatusMessage msg = new DeliveryStatusMessage(ctx);
msg.setArrival(ctx.clock().now());
msg.setMessageId(replyToken);
return msg;
}
/**
* As of 0.9.12, encrypt to hide it from the target and the return path OBEP and IBGW.
* Wrap a DSM in a GarlicMessage, add the fake session to the SKM.
*
* @param skm encrypt dsm with this skm non-null
* @return null on error
* @since 0.9.12
*/
private static GarlicMessage wrapDSM(RouterContext ctx, SessionKeyManager skm, DeliveryStatusMessage dsm) {
// garlic route that DeliveryStatusMessage to ourselves so the endpoints and gateways
// can't tell its a test. to simplify this, we encrypt it with a random key and tag,
// remembering that key+tag so that we can decrypt it later. this means we can do the
// garlic encryption without any ElGamal (yay)
MessageWrapper.OneTimeSession sess = MessageWrapper.generateSession(ctx, skm);
if (sess == null)
return null;
GarlicMessage msg = MessageWrapper.wrap(ctx, dsm, sess);
return msg;
}
/**
* Build a clove that sends the payload to the destination
*/
static PayloadGarlicConfig buildDataClove(RouterContext ctx, Payload data, Destination dest, long expiration) {
PayloadGarlicConfig clove = new PayloadGarlicConfig();
private static PayloadGarlicConfig buildDataClove(RouterContext ctx, Payload data, Destination dest, long expiration) {
DeliveryInstructions instructions = new DeliveryInstructions();
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION);
@@ -203,6 +280,7 @@ class OutboundClientMessageJobHelper {
//instructions.setDelaySeconds(0);
//instructions.setEncrypted(false);
PayloadGarlicConfig clove = new PayloadGarlicConfig();
clove.setCertificate(Certificate.NULL_CERT);
clove.setDeliveryInstructions(instructions);
clove.setExpiration(expiration);
@@ -221,7 +299,7 @@ class OutboundClientMessageJobHelper {
/**
* Build a clove that stores the leaseSet locally
*/
static PayloadGarlicConfig buildLeaseSetClove(RouterContext ctx, long expiration, LeaseSet replyLeaseSet) {
private static PayloadGarlicConfig buildLeaseSetClove(RouterContext ctx, long expiration, LeaseSet replyLeaseSet) {
PayloadGarlicConfig clove = new PayloadGarlicConfig();
clove.setCertificate(Certificate.NULL_CERT);
clove.setDeliveryInstructions(DeliveryInstructions.LOCAL);

View File

@@ -189,7 +189,7 @@ public class MessageWrapper {
*
* @since 0.9.9
*/
private static OneTimeSession generateSession(RouterContext ctx, SessionKeyManager skm) {
public static OneTimeSession generateSession(RouterContext ctx, SessionKeyManager skm) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionTag tag = new SessionTag(true);
Set<SessionTag> tags = new RemovableSingletonSet<SessionTag>(tag);
@@ -197,6 +197,20 @@ public class MessageWrapper {
return new OneTimeSession(key, tag);
}
/**
* Garlic wrap a message from nobody, destined for an unknown router,
* to hide the contents from the IBGW.
* Uses a supplied one-time session key tag for AES encryption,
* avoiding ElGamal.
*
* @param session non-null
* @return null on encrypt failure
* @since 0.9.12
*/
public static GarlicMessage wrap(RouterContext ctx, I2NPMessage m, OneTimeSession session) {
return wrap(ctx, m, session.key, session.tag);
}
/**
* Garlic wrap a message from nobody, destined for an unknown router,
* to hide the contents from the IBGW.