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;