From 9307cc8a0c1df2f82bcf63f1e3b89591e8d4362e Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Tue, 31 Mar 2020 18:43:52 +0000
Subject: [PATCH] Ratchet: Add support for database lookup replies (proposal
 154)

---
 .../i2p/data/i2np/DatabaseLookupMessage.java  | 136 +++++++++++++++---
 1 file changed, 118 insertions(+), 18 deletions(-)

diff --git a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
index ae77921372..b3a7b43392 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
@@ -15,13 +15,15 @@ import java.util.List;
 import java.util.Set;
 
 import net.i2p.I2PAppContext;
+import net.i2p.crypto.EncType;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
-import net.i2p.data.router.RouterInfo;
+import net.i2p.data.PublicKey;
 import net.i2p.data.SessionKey;
 import net.i2p.data.SessionTag;
 import net.i2p.data.TunnelId;
-//import net.i2p.util.Log;
+import net.i2p.data.router.RouterInfo;
+import net.i2p.router.crypto.ratchet.RatchetSessionTag;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -31,7 +33,6 @@ import net.i2p.util.VersionComparator;
  * @author jrandom
  */
 public class DatabaseLookupMessage extends FastI2NPMessageImpl {
-    //private final static Log _log = new Log(DatabaseLookupMessage.class);
     public final static int MESSAGE_TYPE = 2;
     private Hash _key;
     private Hash _fromHash;
@@ -40,6 +41,8 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
     private List<Hash> _dontIncludePeers;
     private SessionKey _replyKey;
     private SessionTag _replyTag;
+    private RatchetSessionTag _ratchetReplyTag;
+    private PublicKey _ratchetPubKey;
     private Type _type;
     
     //private static volatile long _currentLookupPeriod = 0;
@@ -60,6 +63,8 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
     private static final byte FLAG_TYPE_LS = 0x04;
     private static final byte FLAG_TYPE_RI = 0x08;
     private static final byte FLAG_TYPE_EXPL = 0x0c;
+    // 0.9.46 or higher
+    private static final byte FLAG_RATCHET = 0x10;
 
     /** @since 0.9.16 */
     public enum Type {
@@ -80,6 +85,7 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
      *  the receiver rejecting the whole message as invalid.
      */
     private static final String MIN_ENCRYPTION_VERSION = "0.9.7";
+    private static final String MIN_RATCHET_VERSION = "0.9.46";
 
     public DatabaseLookupMessage(I2PAppContext context) {
         this(context, false);
@@ -219,7 +225,21 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
     }
     
     /**
-     *  The included session key or null if unset
+     *  Does this router support ratchet replies?
+     *
+     *  @param to null OK
+     *  @since 0.9.46
+     */
+    public static boolean supportsRatchetReplies(RouterInfo to) {
+        if (to == null)
+            return false;
+        String v = to.getVersion();
+        return VersionComparator.comp(v, MIN_RATCHET_VERSION) >= 0;
+    }
+    
+    /**
+     *  The included session key or null if unset.
+     *  If non-null, either getReplyTag() or getRatchetReplyTag() is non-null.
      *
      *  @since 0.9.7
      */
@@ -241,12 +261,57 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
      *  @since 0.9.7
      */
     public void setReplySession(SessionKey encryptKey, SessionTag encryptTag) {
-        if (_replyKey != null || _replyTag != null)
+        if (_replyKey != null || _replyTag != null ||
+            _ratchetReplyTag != null || _ratchetPubKey != null)
             throw new IllegalStateException();
         _replyKey = encryptKey;
         _replyTag = encryptTag;
     }
     
+    /**
+     *  The included session tag or null if unset
+     *
+     *  @since 0.9.46
+     */
+    public RatchetSessionTag getRatchetReplyTag() { return _ratchetReplyTag; }
+
+    /**
+     *  Ratchet
+     *
+     *  @throws IllegalStateException if key or tag previously set, to protect saved checksum
+     *  @param encryptKey non-null
+     *  @param encryptTag non-null
+     *  @since 0.9.46
+     */
+    public void setReplySession(SessionKey encryptKey, RatchetSessionTag encryptTag) {
+        if (_replyKey != null || _replyTag != null ||
+            _ratchetReplyTag != null || _ratchetPubKey != null)
+            throw new IllegalStateException();
+        _replyKey = encryptKey;
+        _ratchetReplyTag = encryptTag;
+    }
+    
+    /**
+     *  The included session key or null if unset.
+     *  Preliminary, not fully supported, see proposal 154.
+     *
+     *  @since 0.9.46
+     */
+    public PublicKey getRatchetPublicKey() { return _ratchetPubKey; }
+
+    /**
+     *  Ratchet.
+     *  Preliminary, not fully supported, see proposal 154.
+     *
+     *  @throws IllegalStateException if key or tag previously set, to protect saved checksum
+     *  @param encryptKey non-null
+     *  @param encryptTag non-null
+     *  @since 0.9.46
+     */
+    public void setReplySession(PublicKey pubKey) {
+        _ratchetPubKey = pubKey;
+    }
+    
     /**
      * Set of peers that a lookup reply should NOT include.
      * WARNING - returns a copy.
@@ -329,6 +394,7 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
         // TODO store the whole flag byte
         boolean tunnelSpecified = (data[curIndex] & FLAG_TUNNEL) != 0;
         boolean replyKeySpecified = (data[curIndex] & FLAG_ENCRYPT) != 0;
+        boolean ratchetSpecified = (data[curIndex] & FLAG_RATCHET) != 0;
         switch (data[curIndex] & FLAG_TYPE_MASK) {
             case FLAG_TYPE_LS:
                 _type = Type.LS;
@@ -365,20 +431,31 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
             peers.add(p);
         }
         _dontIncludePeers = peers;
-        if (replyKeySpecified) {
+        if (replyKeySpecified || ratchetSpecified) {
+            // all 3 flavors are 32 bytes
             byte[] rk = new byte[SessionKey.KEYSIZE_BYTES];
             System.arraycopy(data, curIndex, rk, 0, SessionKey.KEYSIZE_BYTES);
-            _replyKey = new SessionKey(rk);
+            if (replyKeySpecified && ratchetSpecified)
+                _ratchetPubKey = new PublicKey(EncType.ECIES_X25519, rk);
+            else
+                _replyKey = new SessionKey(rk);
             curIndex += SessionKey.KEYSIZE_BYTES;
-            // number of tags, assume always 1 for now
-            curIndex++;
-            byte[] rt = new byte[SessionTag.BYTE_LENGTH];
-            System.arraycopy(data, curIndex, rt, 0, SessionTag.BYTE_LENGTH);
-            _replyTag = new SessionTag(rt);
+            if (!(replyKeySpecified && ratchetSpecified)) {
+                // number of tags, assume always 1 for now
+                curIndex++;
+                if (replyKeySpecified) {
+                    byte[] rt = new byte[SessionTag.BYTE_LENGTH];
+                    System.arraycopy(data, curIndex, rt, 0, SessionTag.BYTE_LENGTH);
+                    _replyTag = new SessionTag(rt);
+                } else {
+                    byte[] rt = new byte[RatchetSessionTag.LENGTH];
+                    System.arraycopy(data, curIndex, rt, 0, RatchetSessionTag.LENGTH);
+                    _ratchetReplyTag = new RatchetSessionTag(rt);
+                }
+            }
         }
     }
 
-    
     protected int calculateWrittenLength() {
         int totalLength = 0;
         totalLength += Hash.HASH_LENGTH*2; // key+fromHash
@@ -388,9 +465,17 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
         totalLength += 2; // numPeers
         if (_dontIncludePeers != null) 
             totalLength += Hash.HASH_LENGTH * _dontIncludePeers.size();
-        if (_replyKey != null)
+        if (_replyKey != null) {
             // number of tags, assume always 1 for now
-            totalLength += SessionKey.KEYSIZE_BYTES + 1 + SessionTag.BYTE_LENGTH;
+            totalLength += SessionKey.KEYSIZE_BYTES + 1;
+            if (_ratchetReplyTag != null)
+                totalLength += RatchetSessionTag.LENGTH;
+            else
+                totalLength += SessionTag.BYTE_LENGTH;
+        } else if (_ratchetPubKey != null) {
+            totalLength += 32;
+            // no tags
+        }
         return totalLength;
     }
     
@@ -404,8 +489,12 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
         curIndex += Hash.HASH_LENGTH;
         // Generate the flag byte
         byte flag;
-        if (_replyKey != null)
+        if (_replyTag != null)
             flag = FLAG_ENCRYPT;
+        else if (_ratchetReplyTag != null)
+            flag = FLAG_RATCHET;
+        else if (_ratchetPubKey != null)
+            flag = FLAG_RATCHET | FLAG_ENCRYPT;
         else
             flag = 0;
         switch (_type) {
@@ -450,8 +539,17 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
             curIndex += SessionKey.KEYSIZE_BYTES;
             // number of tags, always 1 for now
             out[curIndex++] = 1;
-            System.arraycopy(_replyTag.getData(), 0, out, curIndex, SessionTag.BYTE_LENGTH);
-            curIndex += SessionTag.BYTE_LENGTH;
+            if (_replyTag != null) {
+                System.arraycopy(_replyTag.getData(), 0, out, curIndex, SessionTag.BYTE_LENGTH);
+                curIndex += SessionTag.BYTE_LENGTH;
+            } else {
+                System.arraycopy(_ratchetReplyTag.getData(), 0, out, curIndex, RatchetSessionTag.LENGTH);
+                curIndex += RatchetSessionTag.LENGTH;
+            }
+        } else if (_ratchetPubKey != null) {
+            System.arraycopy(_ratchetPubKey.getData(), 0, out, curIndex, _ratchetPubKey.length());
+            curIndex += _ratchetPubKey.length();
+            // no tags
         }
         return curIndex;
     }
@@ -499,6 +597,8 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
             buf.append("\n\tReply Key: ").append(_replyKey);
         if (_replyTag != null)
             buf.append("\n\tReply Tag: ").append(_replyTag);
+        else if (_ratchetReplyTag != null)
+            buf.append("\n\tRatchetReply Tag: ").append(_ratchetReplyTag);
         if (_dontIncludePeers != null) {
             buf.append("\n\tDon't Include Peers: ");
             buf.append(_dontIncludePeers.size());
-- 
GitLab