Author: toad
Date: 2007-03-28 00:27:21 +0000 (Wed, 28 Mar 2007)
New Revision: 12395

Modified:
   trunk/freenet/src/freenet/crypt/DSASignature.java
   trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java
   trunk/freenet/src/freenet/node/FNPPacketMangler.java
   trunk/freenet/src/freenet/node/Node.java
Log:
Implement something vaguely resembling STS:
- Our current setup is basically ephemeral diffie-hellman, but stage 2 is split 
into stage 2 (g^y) and stage 4 (E_k(...))
- Simply sign stages 3 and 4 (both of which include encrypted data). Include 
the g^y and g^x in the signature.

Modified: trunk/freenet/src/freenet/crypt/DSASignature.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DSASignature.java   2007-03-27 22:56:54 UTC 
(rev 12394)
+++ trunk/freenet/src/freenet/crypt/DSASignature.java   2007-03-28 00:27:21 UTC 
(rev 12395)
@@ -69,5 +69,31 @@
                        toStringCached = HexUtil.biToHex(r) + ',' + 
HexUtil.biToHex(s);
         return toStringCached;
     }
+
+       public byte[] getRBytes(int length) {
+               return getParamBytes(r, length);
+       }
+
+       public byte[] getSBytes(int length) {
+               return getParamBytes(s, length);
+       }
+
+       private static byte[] getParamBytes(BigInteger param, int length) {
+               byte[] data = param.toByteArray();
+               if(data.length < length) {
+                       byte[] out = new byte[length];
+                       System.arraycopy(data, 0, out, out.length - 
data.length, data.length);
+                       return out;
+               } else if(data.length == length+1) {
+                       if(data[0] == 0) {
+                               byte[] out = new byte[length];
+                               System.arraycopy(data, 1, out, 0, length);
+                               return out;
+                       } else
+                               throw new IllegalArgumentException("Parameter 
longer than "+length+" bytes : "+param.bitLength());
+               } else if(data.length == length) {
+                       return data;
+               } else throw new IllegalArgumentException("Length is much 
shorter: "+data.length+" but target length = "+length);
+       }

 }

Modified: trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java   2007-03-27 
22:56:54 UTC (rev 12394)
+++ trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java   2007-03-28 
00:27:21 UTC (rev 12395)
@@ -82,4 +82,8 @@
     public boolean canGetCipher() {
         return peerExponential != null;
     }
+
+       public NativeBigInteger getHisExponential() {
+               return peerExponential;
+       }
 }

Modified: trunk/freenet/src/freenet/node/FNPPacketMangler.java
===================================================================
--- trunk/freenet/src/freenet/node/FNPPacketMangler.java        2007-03-27 
22:56:54 UTC (rev 12394)
+++ trunk/freenet/src/freenet/node/FNPPacketMangler.java        2007-03-28 
00:27:21 UTC (rev 12395)
@@ -9,6 +9,8 @@
 import net.i2p.util.NativeBigInteger;

 import freenet.crypt.BlockCipher;
+import freenet.crypt.DSA;
+import freenet.crypt.DSASignature;
 import freenet.crypt.DiffieHellman;
 import freenet.crypt.DiffieHellmanContext;
 import freenet.crypt.EntropySource;
@@ -250,8 +252,14 @@
         if((negType < 0) || (negType > 1)) {
             Logger.error(this, "Decrypted auth packet but unknown negotiation 
type "+negType+" from "+replyTo+" possibly from "+pn);
             return;
-        }else if (negType == 0){
-               // We are gonna do unauthenticated DH FIXME: disable when StS 
is deployed.
+        }else if (negType == 0 || negType == 1){
+               // Four stage Diffie-Hellman. 0 = ephemeral, 1 = payload stages 
are signed (not quite STS)
+               // FIXME reduce to 3 stages and implement STS properly (we have 
a separate validation mechanism in PeerNode)
+               // AFAICS this (with negType=1) is equivalent in security to 
STS; it expands the second phase into a second and a fourth phase.
+               // A -> B g^x
+               // B -> A g^y
+               // A -> B E^k ( ... )
+               // B -> A E^k ( ... )

                if((packetType < 0) || (packetType > 3)) {
                        Logger.error(this, "Decrypted auth packet but unknown 
packet type "+packetType+" from "+replyTo+" possibly from "+pn);
@@ -281,14 +289,17 @@
                                processDHZeroOrOne(0, payload, pn);
                        if(ctx == null) return;
                        // Send reply
-                       sendFirstHalfDHPacket(1, ctx.getOurExponential(), pn, 
replyTo);
+                       sendFirstHalfDHPacket(negType, 1, 
ctx.getOurExponential(), pn, replyTo);
                        // Send a type 1, they will reply with a type 2
                } else if(packetType == 1) {
                        // We are Alice
                        DiffieHellmanContext ctx = 
                                processDHZeroOrOne(1, payload, pn);
                        if(ctx == null) return;
-                       sendDHCompletion(2, ctx.getCipher(), pn, replyTo);
+                       if(negType == 0)
+                               sendDHCompletion(2, ctx.getCipher(), pn, 
replyTo);
+                       else //if(negType == 1)
+                               sendSignedDHCompletion(2, ctx.getCipher(), pn, 
replyTo, ctx);
                        // Send a type 2
                } else if(packetType == 2) {
                        // We are Bob
@@ -296,79 +307,11 @@
                        // Verify the packet, then complete
                        // Format: IV E_K ( H(data) data )
                        // Where data = [ long: bob's startup number ]
-                       processDHTwoOrThree(2, payload, pn, replyTo, true);
+                       processDHTwoOrThree(2, payload, pn, replyTo, true, 
negType == 1);
                } else if(packetType == 3) {
                        // We are Alice
-                       processDHTwoOrThree(3, payload, pn, replyTo, false);
+                       processDHTwoOrThree(3, payload, pn, replyTo, false, 
negType == 1);
                }
-        }else if (negType == 1){
-               // We are gonna do simple StS
-        
-               if((packetType < 0) || (packetType > 3)) {
-                       Logger.error(this, "Decrypted auth packet but unknown 
packet type "+packetType+" from "+replyTo+" possibly from "+pn);
-                       return;
-               }
-               //  We keep one StationToStationContext per node ONLY
-               /*
-                * Now, to the real meat
-                * 
-                *  1a. Alice generates a random number x and computes and 
sends the exponential g^x to Bob.
-                *  
-                *      2a. Bob generates a random number y and computes the 
exponential g^y.    
-                *      2b. Bob computes the shared secret key K = (g^x)^y.
-                *      2c. Bob concatenates the exponentials (g^y, g^x) (order 
is important),
-                *        signs them using his asymmetric key B, and then 
encrypts them with K.
-                *  2d. He sends the ciphertext along with his own exponential 
g^y to Alice.
-                *  
-                *  3a. Alice computes the shared secret key K = (gy)x.
-                *  3b. Alice decrypts and verifies Bob's signature.
-                *  3c. Alice concatenates the exponentials (g^x, g^y) (order 
is important),
-                *    signs them using her asymmetric key A, and then encrypts 
them with K.
-                *  3d. She sends the ciphertext to Bob.
-                *  
-                *  4. Bob decrypts and verifies Alice's signature.
-                * 
-                *      Alice and Bob are now mutually authenticated and have a 
shared secret.
-                *  This secret, K, can then be used to encrypt further 
communication.
-                *  
-                *  I suggest we add one more phase to simplify the code : 2d 
is splited up
-                *  into two packets so that both alice and bob send the same 
kind of packets. 
-                */
-               
-               // be carefull with StationToStationContext constructors ; it 
will be expensive in terms of cpu and can be DoSed, on the other hand, when 
shall we reset the context ? maybe creating a new packetType ... with a time 
based restriction 
-               if(packetType == 0) { // 0 won't be received, but we need to 
initialize the exchange
-                       StationToStationContext ctx = 
pn.getKeyAgreementSchemeContext()== null ? new 
StationToStationContext(node.getMyPrivKey(), pn.peerCryptoGroup, pn.peerPubKey, 
node.random) : (StationToStationContext)pn.getKeyAgreementSchemeContext();
-                       if(ctx == null) return;
-                       pn.setKeyAgreementSchemeContext(ctx);
-                       // We send g^x
-                       sendFirstStSPacket(1, ctx.getOurExponential(), pn, 
replyTo);
-               } else if(packetType == 2) {
-                       StationToStationContext ctx = 
pn.getKeyAgreementSchemeContext()== null ? new 
StationToStationContext(node.getMyPrivKey(), pn.peerCryptoGroup, pn.peerPubKey, 
node.random) : (StationToStationContext)pn.getKeyAgreementSchemeContext();
-                       if(ctx == null) return;
-                       pn.setKeyAgreementSchemeContext(ctx);
-                       // We got g^y
-                       ctx.setOtherSideExponential(new NativeBigInteger(1, 
payload));
-                       // We send E(S(H( our exponential, his exponential)))
-                       sendSecondStSPacket(3, ctx, pn, replyTo, payload);
-               } else if(packetType == 1) {
-                       StationToStationContext ctx = (StationToStationContext) 
pn.getKeyAgreementSchemeContext();
-                       if(ctx == null) return;
-                       // We got g^x
-                       ctx.setOtherSideExponential(new 
NativeBigInteger(payload));
-                       // We send g^y
-                       sendFirstStSPacket(2, ctx.getOurExponential(), pn, 
replyTo);
-                       // We send E(S(H( our exponential, his exponential)))
-                       sendSecondStSPacket(3, ctx, pn, replyTo, payload);
-               } else if(packetType == 3) {
-                       StationToStationContext ctx = (StationToStationContext) 
pn.getKeyAgreementSchemeContext();
-                       if(ctx == null) return;
-                       if(!ctx.isAuthentificationSuccessfull(payload)) return;
-                       // we are done if the above test is sucessfull!
-               }
-               
-               /*
-                * We need some kind of locking above... and maybe some DoS 
protection
-                */
         }
     }

@@ -411,12 +354,69 @@
     }

     /**
+     * Send a signed DH completion message.
+     * Format:
+     * IV
+     * Signature on { My exponential, his exponential, data }
+     * Data
+     * @param phase The packet phase number. Either 2 or 3.
+     * @param cipher The negotiated cipher.
+     * @param pn The PeerNode which we are talking to.
+     * @param replyTo The Peer to which to send the packet (not necessarily 
the same
+     * as the one on pn as the IP may have changed).
+     */
+    private void sendSignedDHCompletion(int phase, BlockCipher cipher, 
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) {
+        PCFBMode pcfb = PCFBMode.create(cipher);
+        byte[] iv = new byte[pcfb.lengthIV()];
+        
+        byte[] myRef = node.myCompressedSetupRef();
+        byte[] data = new byte[myRef.length + 8];
+        System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0, 8);
+        System.arraycopy(myRef, 0, data, 8, myRef.length);
+        
+        byte[] myExp = ctx.getOurExponential().toByteArray();
+        byte[] hisExp = ctx.getHisExponential().toByteArray();
+        
+        MessageDigest md = SHA256.getMessageDigest();
+        md.update(myExp);
+        md.update(hisExp);
+        md.update(data);
+        byte[] hash = md.digest();
+        
+        DSASignature sig = node.sign(hash);
+        
+        byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        
+        int outputLength = iv.length + data.length + r.length + s.length + 2;
+        
+        byte[] output = new byte[outputLength];
+        
+        System.arraycopy(iv, 0, output, 0, iv.length);
+        int count = iv.length;
+        if(r.length > 255 || s.length > 255)
+               throw new IllegalStateException("R or S is too long: 
r.length="+r.length+" s.length="+s.length);
+        output[count++] = (byte) r.length;
+        System.arraycopy(r, 0, output, count, r.length);
+        count += r.length;
+        output[count++] = (byte) s.length;
+        System.arraycopy(s, 0, output, count, s.length);
+        count += s.length;
+        System.arraycopy(data, 0, output, count, data.length);
+        
+        pcfb.blockEncipher(output, 0, output.length);
+        
+        sendAuthPacket(1, 1, phase, output, pn, replyTo);
+    }
+
+    /**
      * Send a first-half (phase 0 or 1) DH negotiation packet to the node.
      * @param phase The phase of the message to be sent (0 or 1).
-     * @param integer
-     * @param replyTo
+     * @param negType The negotiation type.
+     * @param integer Our exponential
+     * @param replyTo The peer to reply to
      */
-    private void sendFirstHalfDHPacket(int phase, NativeBigInteger integer, 
PeerNode pn, Peer replyTo) {
+    private void sendFirstHalfDHPacket(int phase, int negType, 
NativeBigInteger integer, PeerNode pn, Peer replyTo) {
         long time1 = System.currentTimeMillis();
         if(logMINOR) Logger.minor(this, "Sending ("+phase+") 
"+integer.toHexString()+" to "+pn.getPeer());
         byte[] data = integer.toByteArray();
@@ -438,7 +438,7 @@
         if((time2 - time1) > 200) {
           Logger.error(this, "sendFirstHalfDHPacket: time2 is more than 200ms 
after time1 ("+(time2 - time1)+") working on "+replyTo+" of "+pn.getName());
         }
-        sendAuthPacket(1, 0, phase, data, pn, replyTo);
+        sendAuthPacket(1, negType, phase, data, pn, replyTo);
         long time3 = System.currentTimeMillis();
         if((time3 - time2) > 500) {
           Logger.error(this, "sendFirstHalfDHPacket:sendAuthPacket() time3 is 
more than half a second after time2 ("+(time3 - time2)+") working on 
"+replyTo+" of "+pn.getName());
@@ -511,14 +511,14 @@
        node.outputThrottle.forceGrab(data.length - alreadyReportedBytes);
        }

-       /**
-     * @param i
-     * @param payload
-     * @param pn
-     * @param replyTo
-     * @return
-     */
-    private DiffieHellmanContext processDHTwoOrThree(int i, byte[] payload, 
PeerNode pn, Peer replyTo, boolean sendCompletion) {
+    private DiffieHellmanContext processDHTwoOrThree(int phase, byte[] 
payload, PeerNode pn, Peer replyTo, boolean sendCompletion, boolean signed) {
+       if(signed)
+               return processSignedDHTwoOrThree(phase, payload, pn, replyTo, 
sendCompletion);
+       else
+               return processDHTwoOrThree(phase, payload, pn, replyTo, 
sendCompletion);
+    }
+    
+    private DiffieHellmanContext processDHTwoOrThree(int phase, byte[] 
payload, PeerNode pn, Peer replyTo, boolean sendCompletion) {
         DiffieHellmanContext ctx = (DiffieHellmanContext) 
pn.getKeyAgreementSchemeContext();
         if((ctx == null) || !ctx.canGetCipher()) {
             if(shouldLogErrorInHandshake()) {
@@ -531,7 +531,7 @@
         PCFBMode pcfb = PCFBMode.create(cipher);
         int ivLength = pcfb.lengthIV();
         if(payload.length-3 < HASH_LENGTH + ivLength + 8) {
-            Logger.error(this, "Too short phase "+i+" packet from "+replyTo+" 
probably from "+pn);
+            Logger.error(this, "Too short phase "+phase+" packet from 
"+replyTo+" probably from "+pn);
             return null;
         }
         pcfb.reset(payload, 3); // IV
@@ -557,7 +557,7 @@
             // But this should be extremely rare.
             // REDFLAG?
             // We need to send the completion before the PN sends any packets, 
that's all...
-            if(pn.completedHandshake(bootID, data, 8, data.length-8, cipher, 
encKey, replyTo, i == 2)) {
+            if(pn.completedHandshake(bootID, data, 8, data.length-8, cipher, 
encKey, replyTo, phase == 2)) {
                 if(sendCompletion)
                     sendDHCompletion(3, ctx.getCipher(), pn, replyTo);
                 pn.maybeSendInitialMessages();
@@ -570,6 +570,87 @@
     }

     /**
+     * Process a stage 2 or stage 3 auth packet.
+     * Send a signed DH completion message.
+     * Format:
+     * IV
+     * Signature on { My exponential, his exponential, data }
+     * Data
+     * 
+     * May decrypt in place.
+     */
+    private DiffieHellmanContext processSignedDHTwoOrThree(int phase, byte[] 
payload, PeerNode pn, Peer replyTo, boolean sendCompletion) {
+       // Get context, cipher, IV
+        DiffieHellmanContext ctx = (DiffieHellmanContext) 
pn.getKeyAgreementSchemeContext();
+        if((ctx == null) || !ctx.canGetCipher()) {
+            if(shouldLogErrorInHandshake()) {
+                Logger.error(this, "Cannot get cipher");
+            }
+            return null;
+        }
+        byte[] encKey = ctx.getKey();
+        BlockCipher cipher = ctx.getCipher();
+        PCFBMode pcfb = PCFBMode.create(cipher);
+        int ivLength = pcfb.lengthIV();
+        if(payload.length-3 < HASH_LENGTH + ivLength + 8) {
+            Logger.error(this, "Too short phase "+phase+" packet from 
"+replyTo+" probably from "+pn);
+            return null;
+        }
+        pcfb.reset(payload, 3); // IV
+        
+        // Decrypt the rest
+        pcfb.blockDecipher(payload, 3, payload.length - 3);
+        
+        int count = 3 + ivLength;
+        
+        // R
+        int rLen = payload[count++] & 0xFF;
+        byte[] rBytes = new byte[rLen];
+        System.arraycopy(payload, count, rBytes, 0, rLen);
+        count += rLen;
+        NativeBigInteger r = new NativeBigInteger(rBytes);
+        
+        // S
+        int sLen = payload[count++] & 0xFF;
+        byte[] sBytes = new byte[sLen];
+        System.arraycopy(payload, count, sBytes, 0, sLen);
+        count += sLen;
+        NativeBigInteger s = new NativeBigInteger(sBytes);
+        
+        DSASignature sig = new DSASignature(r, s);
+        
+        // Data
+        byte[] data = new byte[payload.length - count];
+        System.arraycopy(payload, count, data, 0, payload.length - 
data.length);
+        
+        // Now verify
+        MessageDigest md = SHA256.getMessageDigest();
+        md.update(ctx.getHisExponential().toByteArray());
+        md.update(ctx.getOurExponential().toByteArray());
+        md.update(data);
+        byte[] hash = md.digest();
+        if(!node.verify(hash, sig)) {
+               Logger.error(this, "Signature verification failed");
+               return null;
+        }
+        
+        // Success!
+        long bootID = Fields.bytesToLong(data);
+        // Send the completion before parsing the data, because this is easiest
+        // Doesn't really matter - if it fails, we get loads of errors 
anyway...
+        // Only downside is that the other side might still think we are 
connected for a while.
+        // But this should be extremely rare.
+        // REDFLAG?
+        // We need to send the completion before the PN sends any packets, 
that's all...
+        if(pn.completedHandshake(bootID, data, 8, data.length-8, cipher, 
encKey, replyTo, phase == 2)) {
+               if(sendCompletion)
+                       sendSignedDHCompletion(3, ctx.getCipher(), pn, replyTo, 
ctx);
+               pn.maybeSendInitialMessages();
+        }
+        return ctx;
+    }
+
+    /**
      * Should we log an error for an event that could easily be
      * caused by a handshake across a restart boundary?
      */
@@ -1466,7 +1547,8 @@
         * @see 
freenet.node.OutgoingPacketMangler#sendHandshake(freenet.node.PeerNode)
         */
     public void sendHandshake(PeerNode pn) {
-       if(logMINOR) Logger.minor(this, "Possibly sending handshake to "+pn);
+       int negType = pn.bestNegType(this);
+       if(logMINOR) Logger.minor(this, "Possibly sending handshake to "+pn+" 
negotiation type "+negType);
         DiffieHellmanContext ctx;
         Peer[] handshakeIPs;
         if(!pn.shouldSendHandshake()) {
@@ -1510,7 +1592,7 @@
                long innerLoopTime2 = System.currentTimeMillis();
                if((innerLoopTime2 - innerLoopTime1) > 500)
                        Logger.normal(this, "innerLoopTime2 is more than half a 
second after innerLoopTime1 ("+(innerLoopTime2 - innerLoopTime1)+") working on 
"+handshakeIPs[i]+" of "+pn.getName());
-               sendFirstHalfDHPacket(0, ctx.getOurExponential(), pn, 
handshakeIPs[i]);
+               sendFirstHalfDHPacket(negType, 0, ctx.getOurExponential(), pn, 
handshakeIPs[i]);
                long innerLoopTime3 = System.currentTimeMillis();
                if((innerLoopTime3 - innerLoopTime2) > 500)
                        Logger.normal(this, "innerLoopTime3 is more than half a 
second after innerLoopTime2 ("+(innerLoopTime3 - innerLoopTime2)+") working on 
"+handshakeIPs[i]+" of "+pn.getName());

Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2007-03-27 22:56:54 UTC (rev 
12394)
+++ trunk/freenet/src/freenet/node/Node.java    2007-03-28 00:27:21 UTC (rev 
12395)
@@ -28,6 +28,8 @@
 import java.util.Iterator;
 import java.util.zip.DeflaterOutputStream;

+import net.i2p.util.NativeBigInteger;
+
 import org.tanukisoftware.wrapper.WrapperManager;

 import com.sleepycat.je.DatabaseException;
@@ -2275,6 +2277,8 @@
        final LRUQueue recentlyCompletedIDs;

        static final int MAX_RECENTLY_COMPLETED_IDS = 10*1000;
+       /** Length of signature parameters R and S */
+       static final int SIGNATURE_PARAMETER_LENGTH = 32;

        /**
         * Has a request completed with this ID recently?
@@ -2777,4 +2781,14 @@
                        return outputBandwidthLimit * 4;
                return inputBandwidthLimit;
        }
+
+       /** Sign a hash */
+       DSASignature sign(byte[] hash) {
+               return DSA.sign(myCryptoGroup, myPrivKey, new 
NativeBigInteger(1, hash), random);
+       }
+
+       /** Verify a hash */
+       public boolean verify(byte[] hash, DSASignature sig) {
+               return DSA.verify(this.myPubKey, sig, new NativeBigInteger(1, 
hash), false);
+       }
 }


Reply via email to