Update of /cvsroot/freenet/Freenet0.7Rewrite/src/freenet/keys
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7319/src/freenet/keys

Modified Files:
        Key.java NodeCHK.java 
Added Files:
        ClientPublishStreamKey.java PublishStreamKey.java 
Log Message:
Build 136:
Initial publish/subscribe support.
Publish works. Subscribe requests are not yet routed; we just subscribe 
locally, if the stream happens to pass through us.

--- NEW FILE: ClientPublishStreamKey.java ---
package freenet.keys;

import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Random;

import org.spaceroots.mantissa.random.MersenneTwister;

import freenet.crypt.PCFBMode;
import freenet.crypt.RandomSource;
import freenet.crypt.UnsupportedCipherException;
import freenet.crypt.ciphers.Rijndael;
import freenet.support.Fields;
import freenet.support.HexUtil;
import freenet.support.Logger;

/**
 * Key for a publish/subscribe stream. Contains a pubkey,
 * a routing key, and a symmetric crypto key.
 */
public class ClientPublishStreamKey {
    /** The maximum number of bytes in a single packet */
    public final static int SIZE_MAX = 1024;
    static final int SYMMETRIC_KEYLENGTH = 32;

    /** The routing key */
    private final byte[] routingKey;

    /** The content encryption/decryption key */
    private final byte[] symCipherKey;

    /** The encryption/decryption algorithm */
    private final short cryptoAlgorithm;

    private final int hashCode;

    private static final int ROUTING_KEY_LENGTH = 20;
    private static final int SYM_CIPHER_KEY_LENGTH = 32;

    static final short ALGO_AES_PCFB_256 = 1;

    private ClientPublishStreamKey(byte[] routingKey2, byte[] symCipherKey2) {
        routingKey = routingKey2;
        symCipherKey = symCipherKey2;
        hashCode = Fields.hashCode(routingKey) ^ Fields.hashCode(symCipherKey);
        cryptoAlgorithm = ALGO_AES_PCFB_256;
    }

    /**
     * @param uri
     */
    public ClientPublishStreamKey(FreenetURI uri) throws MalformedURLException {
        if(!uri.getKeyType().equals("PSK"))
            throw new MalformedURLException("Not PSK");
        routingKey = uri.getRoutingKey();
        symCipherKey = uri.getCryptoKey();
        byte[] extra = uri.getExtra();
        if(extra == null || extra.length < 2)
            throw new MalformedURLException();
        cryptoAlgorithm = (short)(((extra[0] & 0xff) << 8) + (extra[1] & 0xff));
        if(cryptoAlgorithm != ALGO_AES_PCFB_256) {
            throw new MalformedURLException("Unrecognized crypto algorithm: 
"+cryptoAlgorithm);
        }
        hashCode = Fields.hashCode(routingKey) ^ Fields.hashCode(symCipherKey);
    }

    /**
     * @return The URI for this stream key.
     */
    public FreenetURI getURI() {
        return new FreenetURI("PSK", "", routingKey, symCipherKey, new byte[] { 
(byte)(cryptoAlgorithm>>8), (byte)cryptoAlgorithm });
    }

    public static ClientPublishStreamKey createRandom(RandomSource random) {
        byte[] routingKey = new byte[ROUTING_KEY_LENGTH];
        byte[] symCipherKey = new byte[SYM_CIPHER_KEY_LENGTH];
        random.nextBytes(routingKey);
        random.nextBytes(symCipherKey);
        return new ClientPublishStreamKey(routingKey, symCipherKey);
    }

    /**
     * @return The node-level key
     */
    public PublishStreamKey getKey() {
        return new PublishStreamKey(routingKey);
    }

    // FIXME: add a DSA key to sign messages
    // FIXME: maybe encrypt too?

    public boolean equals(Object o) {
        if(o == this) return true;
        if(o instanceof ClientPublishStreamKey) {
            ClientPublishStreamKey key = (ClientPublishStreamKey)o;
            return Arrays.equals(key.routingKey, routingKey) &&
                Arrays.equals(key.symCipherKey, symCipherKey);
        } else return false;
    }

    public int hashCode() {
        return hashCode;
    }

    /**
     * Encrypt a packet to be sent out on the stream.
     * @param packetNumber
     * @param origData
     * @return
     */
    public byte[] encrypt(long packetNumber, byte[] data, Random random) {
        if(data.length > SIZE_MAX) throw new IllegalArgumentException();
        // Encrypt: IV(32) data(1024) datalength(2)
        byte[] buf = new byte[SYMMETRIC_KEYLENGTH + SIZE_MAX + 2];
        byte[] iv = new byte[SYMMETRIC_KEYLENGTH];
        random.nextBytes(iv);
        System.arraycopy(iv, 0, buf, 0, iv.length);
        System.arraycopy(data, 0, buf, SYMMETRIC_KEYLENGTH, data.length);
        if(data.length != SIZE_MAX) {
            MersenneTwister mt = new MersenneTwister(random.nextLong());
            byte[] temp = new byte[SIZE_MAX - data.length];
            mt.nextBytes(temp);
            System.arraycopy(temp, 0, buf, SYMMETRIC_KEYLENGTH + data.length, 
SIZE_MAX - data.length);
        }
        buf[SYMMETRIC_KEYLENGTH+SIZE_MAX] = (byte)(data.length >> 8);
        buf[SYMMETRIC_KEYLENGTH+SIZE_MAX+1] = (byte)(data.length);
        Logger.minor(this, "Length: "+data.length+": 
"+buf[SYMMETRIC_KEYLENGTH+SIZE_MAX]+","+buf[SYMMETRIC_KEYLENGTH+SIZE_MAX+1]);
        Logger.minor(this, "Plaintext : "+HexUtil.bytesToHex(buf));
        Rijndael r;
        try {
            r = new Rijndael(256,256);
        } catch (UnsupportedCipherException e) {
            throw new Error(e);
        }
        r.initialize(symCipherKey);
        PCFBMode pcfb = new PCFBMode(r);
        pcfb.blockEncipher(buf, 0, buf.length);
        Logger.minor(this, "Ciphertext: "+HexUtil.bytesToHex(buf));
        //Logger.minor(this, "Encrypting with 
"+HexUtil.bytesToHex(symCipherKey));
        return buf;
    }

    /**
     * Decrypt a packet from the stream.
     * @param packetNumber
     * @param packetData
     * @return
     */
    public byte[] decrypt(long packetNumber, byte[] packetData) {
        if(packetData.length != SIZE_MAX + SYMMETRIC_KEYLENGTH + 2) {
            Logger.error(this, "Stream packet wrong size!", new 
Exception("error"));
            return null;
        }
        Rijndael r;
        try {
            r = new Rijndael(256,256);
        } catch (UnsupportedCipherException e) {
            throw new Error(e);
        }
        r.initialize(symCipherKey);
        //Logger.minor(this, "Decrypting with 
"+HexUtil.bytesToHex(symCipherKey));
        PCFBMode pcfb = new PCFBMode(r);
        Logger.minor(this, "Ciphertext: "+HexUtil.bytesToHex(packetData));
        pcfb.blockDecipher(packetData, 0, packetData.length);
        Logger.minor(this, "Plaintext : "+HexUtil.bytesToHex(packetData));
        // Discard first SYMMETRIC_KEYLENGTH bytes (IV)
        int len1 = (packetData[SYMMETRIC_KEYLENGTH+SIZE_MAX] & 0xff);
        int len2 = (packetData[SYMMETRIC_KEYLENGTH+SIZE_MAX+1] & 0xff);
        int length = (len1 << 8) + len2;

        if(length > SIZE_MAX) {
            Logger.error(this, "Could not decrypt packet: length="+length+" 
("+len1+","+len2+")");
            return null;
        }

        byte[] output = new byte[length];
        System.arraycopy(packetData, SYMMETRIC_KEYLENGTH, output, 0, length);
        return output;
    }
}

--- NEW FILE: PublishStreamKey.java ---
package freenet.keys;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import freenet.support.Fields;
import freenet.support.HexUtil;
import freenet.support.Logger;

public class PublishStreamKey extends Key {

    public static final short TYPE = 0x0401;
    static final short ALGO_AES_PCFB_256 = 1;
    static final int KEY_LENGTH = 20;

    private final byte[] routingKey;
    private final int hashCode;
    private boolean hasSecureLongHashCode;
    private long secureLongHashCode;
    double cachedNormalizedDouble;

    public PublishStreamKey(byte[] routingKey) {
        this.routingKey = routingKey;
        hashCode = Fields.hashCode(routingKey);
    }

    public void write(DataOutput _index) throws IOException {
        _index.writeShort(TYPE);
        _index.write(routingKey);
    }

    public boolean equals(Object o) {
        if(o == this) return true;
        if(o instanceof PublishStreamKey) {
            PublishStreamKey k = (PublishStreamKey) o;
            return Arrays.equals(k.routingKey, routingKey);
        } else return false;
    }

    public int hashCode() {
        return hashCode;
    }

    public String toString() {
        return super.toString()+":"+HexUtil.bytesToHex(routingKey);
    }

    /**
     * @return A secure (given the length) 64-bit hash of
     * the key.
     */
    public synchronized long secureLongHashCode() {
        if(hasSecureLongHashCode) return secureLongHashCode;
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            Logger.error(this, "No such algorithm: SHA-1 !!!!!", e);
            throw new RuntimeException(e);
        }
        md.update((byte)(TYPE >> 8));
        md.update((byte)TYPE);
        md.update(routingKey);
        byte[] digest = md.digest();
        secureLongHashCode = Fields.bytesToLong(digest);
        hasSecureLongHashCode = true;
        return secureLongHashCode;
    }

    public double toNormalizedDouble() {
        if(cachedNormalizedDouble > 0) return cachedNormalizedDouble;
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new Error(e);
        }
        md.update(routingKey);
        md.update((byte)(TYPE >> 8));
        md.update((byte)TYPE);
        byte[] digest = md.digest();
        long asLong = Math.abs(Fields.bytesToLong(digest));
        // Math.abs can actually return negative...
        if(asLong == Long.MIN_VALUE)
            asLong = Long.MAX_VALUE;
        cachedNormalizedDouble = ((double)asLong)/((double)Long.MAX_VALUE);
        return cachedNormalizedDouble;
    }

    public void writeToDataOutputStream(DataOutputStream stream) throws 
IOException {
        stream.writeShort(TYPE);
        stream.write(routingKey);
    }

    public static Key read(DataInput raf, boolean readType) throws IOException {
        if(readType) {
            short type = raf.readShort();
            if(type != TYPE)
                // FIXME there has to be a better way to deal with format 
errors...
                throw new IOException("Invalid type");
        }
        byte[] buf = new byte[KEY_LENGTH];
        raf.readFully(buf);
        return new PublishStreamKey(buf);
    }
}

Index: Key.java
===================================================================
RCS file: /cvsroot/freenet/Freenet0.7Rewrite/src/freenet/keys/Key.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -w -r1.3 -r1.4
--- Key.java    20 Aug 2005 21:21:21 -0000      1.3
+++ Key.java    15 Sep 2005 18:16:04 -0000      1.4
@@ -4,12 +4,14 @@
 import java.io.DataOutput;
 import java.io.IOException;

+import freenet.io.WritableToDataOutputStream;
+
 /**
  * @author amphibian
  * 
  * Base class for keys.
  */
-public abstract class Key {
+public abstract class Key implements WritableToDataOutputStream {

     /** 20 bytes for hash, 2 bytes for type */
     public static final short KEY_SIZE_ON_DISK = 22;
@@ -30,7 +32,16 @@
         short type = raf.readShort();
         if(type == NodeCHK.TYPE) {
             return NodeCHK.read(raf);
+        } else if(type == PublishStreamKey.TYPE) {
+            return PublishStreamKey.read(raf);
         }
         throw new IOException("Unrecognized format: "+type);
     }
+    
+    /**
+     * Convert the key to a double between 0.0 and 1.0.
+     * Normally we will hash the key first, in order to
+     * make chosen-key attacks harder.
+     */
+    public abstract double toNormalizedDouble();
 }

Index: NodeCHK.java
===================================================================
RCS file: /cvsroot/freenet/Freenet0.7Rewrite/src/freenet/keys/NodeCHK.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -w -r1.5 -r1.6
--- NodeCHK.java        27 Jul 2005 18:02:06 -0000      1.5
+++ NodeCHK.java        15 Sep 2005 18:16:04 -0000      1.6
@@ -4,7 +4,6 @@
 import java.io.DataOutput;
 import java.io.DataOutputStream;
 import java.io.IOException;
-import java.io.RandomAccessFile;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;

@@ -18,7 +17,7 @@
  * Node-level CHK. Does not have enough information to decode the payload.
  * But can verify that it is intact. Just has the routingKey.
  */
-public class NodeCHK extends Key implements WritableToDataOutputStream {
+public class NodeCHK extends Key {

     final int hash;
     double cachedNormalizedDouble;


Reply via email to