package net.i2p.router.networkdb.kademlia; import java.util.HashSet; import java.util.Set; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TagSetHandle; import net.i2p.data.Certificate; import net.i2p.data.Hash; import net.i2p.data.PublicKey; import net.i2p.data.router.RouterInfo; import net.i2p.data.SessionKey; import net.i2p.data.SessionTag; import net.i2p.data.i2np.DeliveryInstructions; import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.RouterContext; import net.i2p.router.message.GarlicMessageBuilder; import net.i2p.router.message.PayloadGarlicConfig; import net.i2p.router.util.RemovableSingletonSet; /** * Method and class for garlic encrypting outbound netdb traffic, * and sending keys and tags for others to encrypt inbound netdb traffic, * including management of the ElGamal/AES tags. * * @since 0.7.10 */ public class MessageWrapper { //private static final Log _log = RouterContext.getGlobalContext().logManager().getLog(MessageWrapper.class); private static final int NETDB_TAGS_TO_DELIVER = 6; private static final int NETDB_LOW_THRESHOLD = 3; /** * Garlic wrap a message from a client or this router, destined for a router, * to hide the contents from the OBEP. * Caller must call acked() or fail() on the returned object. * * @param from must be a local client with a session key manager, * or null to use the router's session key manager * @return null on encrypt failure */ static WrappedMessage wrap(RouterContext ctx, I2NPMessage m, Hash from, RouterInfo to) { PayloadGarlicConfig payload = new PayloadGarlicConfig(); payload.setCertificate(Certificate.NULL_CERT); payload.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); payload.setPayload(m); payload.setRecipient(to); payload.setDeliveryInstructions(DeliveryInstructions.LOCAL); payload.setExpiration(m.getMessageExpiration()); SessionKeyManager skm; if (from != null) skm = ctx.clientManager().getClientSessionKeyManager(from); else skm = ctx.sessionKeyManager(); if (skm == null) return null; SessionKey sentKey = new SessionKey(); Set<SessionTag> sentTags = new HashSet<SessionTag>(); GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, sentKey, sentTags, NETDB_TAGS_TO_DELIVER, NETDB_LOW_THRESHOLD, skm); if (msg == null) return null; TagSetHandle tsh = null; PublicKey sentTo = to.getIdentity().getPublicKey(); if (!sentTags.isEmpty()) tsh = skm.tagsDelivered(sentTo, sentKey, sentTags); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Sent to: " + to.getIdentity().getHash() + " with key: " + sentKey + " and tags: " + sentTags.size()); return new WrappedMessage(msg, skm, sentTo, sentKey, tsh); } /** * Wrapper so that we can keep track of the key and tags * for later notification to the SKM */ static class WrappedMessage { private GarlicMessage msg; private SessionKeyManager skm; private PublicKey sentTo; private SessionKey sessionKey; private TagSetHandle tsh; WrappedMessage(GarlicMessage msg, SessionKeyManager skm, PublicKey sentTo, SessionKey sentKey, TagSetHandle tsh) { this.msg = msg; this.skm = skm; this.sentTo = sentTo; this.sessionKey = sentKey; this.tsh = tsh; } GarlicMessage getMessage() { return this.msg; } /** delivered tags (if any) were acked */ void acked() { if (this.tsh != null) { this.skm.tagsAcked(this.sentTo, this.sessionKey, this.tsh); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Tags acked for key: " + this.sessionKey); } } /** delivered tags (if any) were not acked */ void fail() { if (this.tsh != null) { this.skm.failTags(this.sentTo, this.sessionKey, this.tsh); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Tags NOT acked for key: " + this.sessionKey); } } } /** * Garlic wrap a message from nobody, destined for a router, * to hide the contents from the OBEP. * Forces ElGamal. * * @return null on encrypt failure * @since 0.9.5 */ static GarlicMessage wrap(RouterContext ctx, I2NPMessage m, RouterInfo to) { PayloadGarlicConfig payload = new PayloadGarlicConfig(); payload.setCertificate(Certificate.NULL_CERT); payload.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); payload.setPayload(m); payload.setRecipient(to); payload.setDeliveryInstructions(DeliveryInstructions.LOCAL); payload.setExpiration(m.getMessageExpiration()); SessionKey sentKey = ctx.keyGenerator().generateSessionKey(); PublicKey key = to.getIdentity().getPublicKey(); GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null, null, key, sentKey, null); return msg; } /** * A single key and tag, for receiving a single message. * * @since 0.9.7 */ public static class OneTimeSession { public final SessionKey key; public final SessionTag tag; public OneTimeSession(SessionKey key, SessionTag tag) { this.key = key; this.tag = tag; } } /** * Create a single key and tag, for receiving a single encrypted message, * and register it with the router's session key manager, to expire in two minutes. * The recipient can then send us an AES-encrypted message, * avoiding ElGamal. * * @since 0.9.7 */ public static OneTimeSession generateSession(RouterContext ctx) { return generateSession(ctx, ctx.sessionKeyManager()); } /** * Create a single key and tag, for receiving a single encrypted message, * and register it with the client's session key manager, to expire in two minutes. * The recipient can then send us an AES-encrypted message, * avoiding ElGamal. * * @return null if we can't find the SKM for the localDest * @since 0.9.9 */ public static OneTimeSession generateSession(RouterContext ctx, Hash localDest) { SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(localDest); if (skm == null) return null; return generateSession(ctx, skm); } /** * Create a single key and tag, for receiving a single encrypted message, * and register it with the given session key manager, to expire in two minutes. * The recipient can then send us an AES-encrypted message, * avoiding ElGamal. * * @return non-null * @since 0.9.9 */ 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); skm.tagsReceived(key, tags, 2*60*1000); 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. * Uses a supplied session key and session tag for AES encryption, * avoiding ElGamal. * * @param encryptKey non-null * @param encryptTag non-null * @return null on encrypt failure * @since 0.9.7 */ public static GarlicMessage wrap(RouterContext ctx, I2NPMessage m, SessionKey encryptKey, SessionTag encryptTag) { PayloadGarlicConfig payload = new PayloadGarlicConfig(); payload.setCertificate(Certificate.NULL_CERT); payload.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); payload.setPayload(m); payload.setDeliveryInstructions(DeliveryInstructions.LOCAL); payload.setExpiration(m.getMessageExpiration()); GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null, null, null, encryptKey, encryptTag); return msg; } }