Author: toad
Date: 2005-12-16 19:01:17 +0000 (Fri, 16 Dec 2005)
New Revision: 7723
Added:
trunk/freenet/src/freenet/crypt/DSA.java
trunk/freenet/src/freenet/crypt/DSAPrivateKey.java
trunk/freenet/src/freenet/crypt/DSAPublicKey.java
trunk/freenet/src/freenet/keys/NodeSSK.java
trunk/freenet/src/freenet/keys/SSKBlock.java
trunk/freenet/src/freenet/keys/SSKVerifyException.java
Modified:
trunk/freenet/src/freenet/keys/CHKBlock.java
trunk/freenet/src/freenet/keys/Key.java
trunk/freenet/src/freenet/keys/KeyBlock.java
trunk/freenet/src/freenet/keys/NodeCHK.java
trunk/freenet/src/freenet/store/DataStore.java
Log:
Beginnings of SSK support.
Still needs more work.
Also need new groups with support for 256-bit hashes and maybe larger pubkeys
too.
Added: trunk/freenet/src/freenet/crypt/DSA.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DSA.java 2005-12-16 15:48:43 UTC (rev
7722)
+++ trunk/freenet/src/freenet/crypt/DSA.java 2005-12-16 19:01:17 UTC (rev
7723)
@@ -0,0 +1,154 @@
+package freenet.crypt;
+
+import java.math.BigInteger;
+import java.util.Random;
+
+import net.i2p.util.NativeBigInteger;
+
+/**
+ * Implements the Digital Signature Algorithm (DSA) described in FIPS-186
+ */
+public class DSA {
+
+ /**
+ * Returns a DSA signature given a group, private key (x), a random nonce
+ * (k), and the hash of the message (m).
+ */
+ public static DSASignature sign(DSAGroup g,
+ DSAPrivateKey x,
+ BigInteger k,
+ BigInteger m) {
+ BigInteger r=g.getG().modPow(k, g.getP()).mod(g.getQ());
+
+ BigInteger kInv=k.modInverse(g.getQ());
+ return sign(g, x, r, kInv, m);
+ }
+
+ public static DSASignature sign(DSAGroup g, DSAPrivateKey x, BigInteger m,
+ Random r) {
+ BigInteger k;
+ do {
+ k=new NativeBigInteger(160, r);
+ } while (k.compareTo(g.getQ())>-1 || k.compareTo(BigInteger.ZERO)==0);
+ return sign(g, x, k, m);
+ }
+
+ /**
+ * Precalculates a number of r, kInv pairs given a random source
+ */
+ public static BigInteger[][] signaturePrecalculate(DSAGroup g,
+ int count, Random r) {
+ BigInteger[][] result=new BigInteger[count][2];
+
+ for (int i=0; i<count; i++) {
+ BigInteger k;
+ do {
+ k=new NativeBigInteger(160, r);
+ } while (k.compareTo(g.getQ())>-1 ||
k.compareTo(BigInteger.ZERO)==0);
+
+ result[i][0] = g.getG().modPow(k, g.getP()); // r
+ result[i][1] = k.modInverse(g.getQ()); // k^-1
+ }
+ return result;
+ }
+
+ /**
+ * Returns a DSA signature given a group, private key (x),
+ * the precalculated values of r and k^-1, and the hash
+ * of the message (m)
+ */
+ public static DSASignature sign(DSAGroup g, DSAPrivateKey x,
+ BigInteger r, BigInteger kInv,
+ BigInteger m) {
+ BigInteger s1=m.add(x.getX().multiply(r)).mod(g.getQ());
+ BigInteger s=kInv.multiply(s1).mod(g.getQ());
+ return new DSASignature(r,s);
+ }
+
+ /**
+ * Verifies the message authenticity given a group, the public key
+ * (y), a signature, and the hash of the message (m).
+ */
+ public static boolean verify(DSAPublicKey kp,
+ DSASignature sig,
+ BigInteger m) {
+ try {
+ BigInteger w=sig.getS().modInverse(kp.getQ());
+ BigInteger u1=m.multiply(w).mod(kp.getQ());
+ BigInteger u2=sig.getR().multiply(w).mod(kp.getQ());
+ BigInteger v1=kp.getG().modPow(u1, kp.getP());
+ BigInteger v2=kp.getY().modPow(u2, kp.getP());
+ BigInteger v=v1.multiply(v2).mod(kp.getP()).mod(kp.getQ());
+ return v.equals(sig.getR());
+
+
+ //FIXME: is there a better way to handle this exception raised on
the 'w=' line above?
+ } catch (ArithmeticException e) { // catch error raised by invalid data
+ return false; // and report that that data is bad.
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ //DSAGroup g=DSAGroup.readFromField(args[0]);
+ DSAGroup g = Global.DSAgroupB;
+ //Yarrow y=new Yarrow();
+ DummyRandomSource y = new DummyRandomSource();
+ DSAPrivateKey pk=new DSAPrivateKey(g, y);
+ DSAPublicKey pub=new DSAPublicKey(g, pk);
+ DSASignature sig=sign(g, pk, BigInteger.ZERO, y);
+ System.err.println(verify(pub, sig, BigInteger.ZERO));
+ while(true) {
+ long totalTimeSigning = 0;
+ long totalTimeVerifying = 0;
+ long totalRSize = 0;
+ long totalSSize = 0;
+ long totalPubKeySize = 0;
+ long totalPrivKeySize = 0;
+ int maxPrivKeySize = 0;
+ int maxPubKeySize = 0;
+ int maxRSize = 0;
+ int maxSSize = 0;
+ int totalRUnsignedBitSize = 0;
+ int maxRUnsignedBitSize = 0;
+ Random r = new Random(y.nextLong());
+ byte[] msg = new byte[32];
+ for(int i=0;i<1000;i++) {
+ r.nextBytes(msg);
+ BigInteger m = new BigInteger(1, msg);
+ pk = new DSAPrivateKey(g, r);
+ int privKeySize = pk.asBytes().length;
+ totalPrivKeySize += privKeySize;
+ if(privKeySize > maxPrivKeySize) maxPrivKeySize = privKeySize;
+ pub = new DSAPublicKey(g, pk);
+ int pubKeySize = pub.asBytes().length;
+ totalPubKeySize += pubKeySize;
+ if(pubKeySize > maxPubKeySize) maxPubKeySize = pubKeySize;
+ long t1 = System.currentTimeMillis();
+ sig = sign(g, pk, m, y);
+ long t2 = System.currentTimeMillis();
+ if(!verify(pub, sig, m)) {
+ System.err.println("Failed to verify!");
+ }
+ long t3 = System.currentTimeMillis();
+ totalTimeSigning += (t2 - t1);
+ totalTimeVerifying += (t3 - t2);
+ int rSize = sig.getR().toByteArray().length;
+ totalRSize += rSize;
+ if(rSize > maxRSize) maxRSize = rSize;
+ int rUnsignedBitSize = sig.getR().bitLength();
+ totalRUnsignedBitSize += rUnsignedBitSize;
+ maxRUnsignedBitSize = Math.max(maxRUnsignedBitSize,
rUnsignedBitSize);
+ int sSize = sig.getS().toByteArray().length;
+ totalSSize += sSize;
+ if(sSize > maxSSize) maxSSize = sSize;
+ }
+ System.out.println("Total time signing: "+totalTimeSigning);
+ System.out.println("Total time verifying: "+totalTimeVerifying);
+ System.out.println("Total R size: "+totalRSize+" (max "+maxRSize+")");
+ System.out.println("Total S size: "+totalSSize+" (max "+maxSSize+")");
+ System.out.println("Total R unsigned bitsize: "+totalRUnsignedBitSize);
+ System.out.println("Total pub key size: "+totalPubKeySize+" (max
"+maxPubKeySize+")");
+ System.out.println("Total priv key size: "+totalPrivKeySize+" (max
"+maxPrivKeySize+")");
+ }
+ }
+}
Added: trunk/freenet/src/freenet/crypt/DSAPrivateKey.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DSAPrivateKey.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/crypt/DSAPrivateKey.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -0,0 +1,70 @@
+package freenet.crypt;
+
+import java.math.BigInteger;
+import java.io.*;
+import java.util.Random;
+
+import freenet.support.HexUtil;
+
+import net.i2p.util.NativeBigInteger;
+
+public class DSAPrivateKey extends CryptoKey {
+
+ private final BigInteger x;
+
+ public DSAPrivateKey(BigInteger x) {
+ this.x = x;
+ }
+
+ // this is dangerous... better to force people to construct the
+ // BigInteger themselves so they know what is going on with the sign
+ //public DSAPrivateKey(byte[] x) {
+ // this.x = new BigInteger(1, x);
+ //}
+
+ public DSAPrivateKey(DSAGroup g, Random r) {
+ BigInteger x;
+ do {
+ x = new NativeBigInteger(160, r);
+ } while (x.compareTo(g.getQ()) > -1);
+ this.x = x;
+ }
+
+ public String keyType() {
+ return "DSA.s";
+ }
+
+ public BigInteger getX() {
+ return x;
+ }
+
+ public static CryptoKey read(InputStream i) throws IOException {
+ return new DSAPrivateKey(Util.readMPI(i));
+ }
+
+ public String writeAsField() {
+ return HexUtil.biToHex(x);
+ }
+
+ // what? why is DSAGroup passed in?
+ //public static CryptoKey readFromField(DSAGroup group, String field) {
+ // //BigInteger x=Util.byteArrayToMPI(Util.hexToBytes(field));
+ // return new DSAPrivateKey(new BigInteger(field, 16));
+ //}
+
+ public byte[] asBytes() {
+ return Util.MPIbytes(x);
+ }
+
+ public byte[] fingerprint() {
+ return fingerprint(new BigInteger[] {x});
+ }
+
+// public static void main(String[] args) throws Exception {
+// Yarrow y=new Yarrow();
+// DSAPrivateKey p=new DSAPrivateKey(Global.DSAgroupC, y);
+// DSAPublicKey pk=new DSAPublicKey(Global.DSAgroupC, p);
+// p.write(System.out);
+// }
+}
+
Added: trunk/freenet/src/freenet/crypt/DSAPublicKey.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DSAPublicKey.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/crypt/DSAPublicKey.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -0,0 +1,153 @@
+/* -*- Mode: java; c-basic-indent: 4; tab-width: 4 -*- */
+package freenet.crypt;
+
+import java.math.BigInteger;
+import java.io.*;
+
+import freenet.support.HexUtil;
+
+import net.i2p.util.NativeBigInteger;
+
+public class DSAPublicKey extends CryptoKey {
+
+ private final BigInteger y;
+ /** A cache of the hexadecimal string representation of y */
+ private final String yAsHexString;
+
+ private final DSAGroup group;
+
+ private byte[] fingerprint = null;
+
+ public DSAPublicKey(DSAGroup g, BigInteger y) {
+ this.y=y;
+ this.yAsHexString = HexUtil.biToHex(y);
+ this.group=g;
+ }
+
+ /**
+ * Use this constructor if you have a Hex:ed version of y already
+ * available, will save some conversions and string allocations.
+ */
+ public DSAPublicKey(DSAGroup g, String yAsHexString) throws
NumberFormatException {
+ this.y=new NativeBigInteger(yAsHexString,16);
+ this.yAsHexString = yAsHexString;
+ this.group=g;
+ }
+
+ public DSAPublicKey(DSAGroup g, DSAPrivateKey p) {
+ this(g,g.getG().modPow(p.getX(), g.getP()));
+ }
+
+ public BigInteger getY() {
+ return y;
+ }
+
+ public String getYAsHexString() {
+ return yAsHexString;
+ }
+
+ public BigInteger getP() {
+ return group.getP();
+ }
+
+ public String getPAsHexString() {
+ return group.getPAsHexString();
+ }
+
+ public BigInteger getQ() {
+ return group.getQ();
+ }
+
+ public String getQAsHexString() {
+ return group.getQAsHexString();
+ }
+
+ public BigInteger getG() {
+ return group.getG();
+ }
+
+ public String getGAsHexString() {
+ return group.getGAsHexString();
+ }
+
+ public String keyType() {
+ return "DSA.p";
+ }
+
+ // Nope, this is fine
+ public DSAGroup getGroup() {
+ return group;
+ }
+
+// public void writeForWireWithoutGroup(OutputStream out) throws
IOException {
+// Util.writeMPI(y, out);
+// }
+//
+// public void writeForWire(OutputStream out) throws IOException {
+// Util.writeMPI(y, out);
+// group.writeForWire(out);
+// }
+//
+// public void writeWithoutGroup(OutputStream out)
+// throws IOException {
+// write(out, getClass().getName());
+// Util.writeMPI(y, out);
+// }
+//
+ public static CryptoKey read(InputStream i) throws IOException {
+ BigInteger y=Util.readMPI(i);
+ DSAGroup g=(DSAGroup)CryptoKey.read(i);
+ return new DSAPublicKey(g, y);
+ }
+
+ public int keyId() {
+ return y.intValue();
+ }
+
+ public String writeAsField() {
+ return yAsHexString;
+ }
+
+ // this won't correctly read the output from writeAsField
+ //public static CryptoKey readFromField(DSAGroup group, String field) {
+ // BigInteger y=Util.byteArrayToMPI(Util.hexToBytes(field));
+ // return new DSAPublicKey(group, y);
+ //}
+
+ public byte[] asBytes() {
+ byte[] groupBytes=group.asBytes();
+ byte[] ybytes=Util.MPIbytes(y);
+ byte[] bytes=new byte[groupBytes.length + ybytes.length];
+ System.arraycopy(groupBytes, 0, bytes, 0, groupBytes.length);
+ System.arraycopy(ybytes, 0, bytes, groupBytes.length,
ybytes.length);
+ return bytes;
+ }
+
+ public byte[] fingerprint() {
+ synchronized(this) {
+ if(fingerprint == null)
+ fingerprint = fingerprint(new BigInteger[] {y});
+ return fingerprint;
+ }
+ }
+
+ public boolean equals(DSAPublicKey o) {
+ if(this == o) // Not necessary, but a very cheap optimization
+ return true;
+ return y.equals(o.y) && group.equals(o.group);
+ }
+
+ public boolean equals(Object o) {
+ if(this == o) // Not necessary, but a very cheap optimization
+ return true;
+ return (o instanceof DSAPublicKey)
+ && y.equals(((DSAPublicKey) o).y)
+ && group.equals(((DSAPublicKey) o).group);
+ }
+
+ public int compareTo(Object other) {
+ if (other instanceof DSAPublicKey) {
+ return getY().compareTo(((DSAPublicKey)other).getY());
+ } else return -1;
+ }
+}
Modified: trunk/freenet/src/freenet/keys/CHKBlock.java
===================================================================
--- trunk/freenet/src/freenet/keys/CHKBlock.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/keys/CHKBlock.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -34,7 +34,6 @@
final short hashIdentifier;
final NodeCHK chk;
public static final int MAX_LENGTH_BEFORE_COMPRESSION = Integer.MAX_VALUE;
- final static int HASH_SHA256 = 1;
public String toString() {
return super.toString()+": chk="+chk;
Modified: trunk/freenet/src/freenet/keys/Key.java
===================================================================
--- trunk/freenet/src/freenet/keys/Key.java 2005-12-16 15:48:43 UTC (rev
7722)
+++ trunk/freenet/src/freenet/keys/Key.java 2005-12-16 19:01:17 UTC (rev
7723)
@@ -3,8 +3,11 @@
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import freenet.io.WritableToDataOutputStream;
+import freenet.support.Fields;
/**
* @author amphibian
@@ -13,9 +16,20 @@
*/
public abstract class Key implements WritableToDataOutputStream {
+ final int hash;
+ double cachedNormalizedDouble;
+ /** Whatever its type, it will need a routingKey ! */
+ final byte[] routingKey;
+
/** 32 bytes for hash, 2 bytes for type */
public static final short KEY_SIZE_ON_DISK = 34;
+ protected Key(byte[] routingKey) {
+ this.routingKey = routingKey;
+ hash = Fields.hashCode(routingKey);
+ cachedNormalizedDouble = -1;
+ }
+
/**
* Write to disk.
* Take up exactly 22 bytes.
@@ -36,10 +50,36 @@
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();
+ public synchronized 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);
+ int TYPE = getType();
+ 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 abstract short getType();
+
+ public int hashCode() {
+ return hash;
+ }
}
Modified: trunk/freenet/src/freenet/keys/KeyBlock.java
===================================================================
--- trunk/freenet/src/freenet/keys/KeyBlock.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/keys/KeyBlock.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -10,6 +10,8 @@
*/
public interface KeyBlock {
+ final static int HASH_SHA256 = 1;
+
/** Decode with the key
* @param key The ClientKey to use to decode the block.
* @param factory The BucketFactory to use to create the Bucket to
return the data in.
Modified: trunk/freenet/src/freenet/keys/NodeCHK.java
===================================================================
--- trunk/freenet/src/freenet/keys/NodeCHK.java 2005-12-16 15:48:43 UTC (rev
7722)
+++ trunk/freenet/src/freenet/keys/NodeCHK.java 2005-12-16 19:01:17 UTC (rev
7723)
@@ -18,21 +18,16 @@
*/
public class NodeCHK extends Key {
- final int hash;
- double cachedNormalizedDouble;
-
public NodeCHK(byte[] routingKey2) {
- routingKey = routingKey2;
+ super(routingKey2);
if(routingKey2.length != KEY_LENGTH)
throw new IllegalArgumentException("Wrong length:
"+routingKey2.length+" should be "+KEY_LENGTH);
- hash = Fields.hashCode(routingKey);
- cachedNormalizedDouble = -1;
}
static final int KEY_LENGTH = 32;
- byte[] routingKey;
- public static final short TYPE = 0x0302;
+ // 01 = CHK, 01 = first version of CHK
+ public static final short TYPE = 0x0101;
/** The size of the data */
public static final int BLOCK_SIZE = 32768;
@@ -55,10 +50,6 @@
return new NodeCHK(buf);
}
- public int hashCode() {
- return hash;
- }
-
public boolean equals(Object key) {
if(key instanceof NodeCHK) {
NodeCHK chk = (NodeCHK) key;
@@ -67,27 +58,7 @@
return false;
}
- /**
- * @return The key, hashed, converted to a double in the range
- * 0.0 to 1.0.
- */
- public synchronized 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 short getType() {
+ return TYPE;
+ }
}
Added: trunk/freenet/src/freenet/keys/NodeSSK.java
===================================================================
--- trunk/freenet/src/freenet/keys/NodeSSK.java 2005-12-16 15:48:43 UTC (rev
7722)
+++ trunk/freenet/src/freenet/keys/NodeSSK.java 2005-12-16 19:01:17 UTC (rev
7723)
@@ -0,0 +1,79 @@
+package freenet.keys;
+
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import freenet.crypt.DSAPublicKey;
+
+/**
+ * An SSK is a Signed Subspace Key.
+ * { pubkey, cryptokey, filename } -> document, basically.
+ * To insert you need the private key corresponding to pubkey.
+ * KSKs are implemented via SSKs.
+ *
+ * This is just the key, so we have the hash of the pubkey, the entire
cryptokey,
+ * and the entire filename.
+ */
+public class NodeSSK extends Key {
+
+ /** Public key hash */
+ final byte[] pubKeyHash;
+ /** E(H(docname)) (E = encrypt using decrypt key, which only clients
know) */
+ final byte[] encryptedHashedDocname;
+ /** The signature key, if we know it */
+ DSAPublicKey pubKey;
+
+ public NodeSSK(byte[] pkHash, byte[] ehDocname) {
+ super(makeRoutingKey(pkHash, ehDocname));
+ this.encryptedHashedDocname = ehDocname;
+ this.pubKeyHash = pkHash;
+ }
+
+ // routingKey = H( E(H(docname)) + H(pubkey) )
+ private static byte[] makeRoutingKey(byte[] pkHash, byte[] ehDocname) {
+ MessageDigest md256;
+ try {
+ md256 = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error(e);
+ }
+ md256.update(ehDocname);
+ md256.update(pkHash);
+ return md256.digest();
+ }
+
+ // 01 = SSK, 01 = first version of SSK
+ public short TYPE = 0x0201;
+
+ public void write(DataOutput _index) throws IOException {
+ _index.writeShort(TYPE);
+ _index.write(encryptedHashedDocname);
+ _index.write(pubKeyHash);
+ }
+
+ public short getType() {
+ return TYPE;
+ }
+
+ public void writeToDataOutputStream(DataOutputStream stream) throws
IOException {
+ write(stream);
+ }
+
+ /**
+ * @return True if we know the pubkey.
+ */
+ public boolean hasPubKey() {
+ return pubKey != null;
+ }
+
+ /**
+ * @return The public key, *if* we know it. Otherwise null.
+ */
+ public DSAPublicKey getPubKey() {
+ return pubKey;
+ }
+
+}
Added: trunk/freenet/src/freenet/keys/SSKBlock.java
===================================================================
--- trunk/freenet/src/freenet/keys/SSKBlock.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/keys/SSKBlock.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -0,0 +1,107 @@
+package freenet.keys;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import net.i2p.util.NativeBigInteger;
+
+import freenet.crypt.DSA;
+import freenet.crypt.DSAPublicKey;
+import freenet.crypt.DSASignature;
+import freenet.support.Bucket;
+import freenet.support.BucketFactory;
+
+/**
+ * SSKBlock. Contains a full fetched key. Can do a node-level verification.
Can
+ * decode original data when fed a ClientSSK.
+ */
+public class SSKBlock implements KeyBlock {
+
+ final byte[] data;
+ final byte[] headers;
+ /** The index of the first non-signature-related byte in the headers */
+ final int headersOffset;
+ /* HEADERS FORMAT:
+ * 2 bytes - hash ID
+ * SIGNATURE ON THE BELOW HASH:
+ * 20 bytes - signature: R (unsigned bytes)
+ * 20 bytes - signature: S (unsigned bytes)
+ * IMPLICIT - hash of remaining fields, including the implicit hash of
data
+ * IMPLICIT - hash of data
+ * 2 bytes - symmetric cipher ID
+ * 32 bytes - E(H(docname))
+ * ENCRYPTED WITH E(H(docname)) AS IV:
+ * 32 bytes - H(decrypted data), = data decryption key
+ * 2 bytes - data length + metadata flag
+ * 2 bytes - data compression algorithm or -1
+ *
+ * PLUS THE PUBKEY:
+ * Pubkey
+ * Group
+ */
+ final NodeSSK nodeKey;
+ final DSAPublicKey pubKey;
+ final short hashIdentifier;
+
+ static final short DATA_LENGTH = 1024;
+
+ static final short SIG_R_LENGTH = 20;
+ static final short SIG_S_LENGTH = 20;
+
+ /**
+ * Initialize, and verify data, headers against key. Provided
+ * key must have a pubkey, or we throw.
+ */
+ public SSKBlock(byte[] data, byte[] headers, NodeSSK nodeKey) throws
SSKVerifyException {
+ this.data = data;
+ this.headers = headers;
+ this.nodeKey = nodeKey;
+ this.pubKey = nodeKey.getPubKey();
+ if(data.length != DATA_LENGTH)
+ throw new SSKVerifyException("Data length wrong:
"+data.length+" should be "+DATA_LENGTH);
+ if(pubKey == null)
+ throw new SSKVerifyException("PubKey was null from
"+nodeKey);
+ if(headers.length < 2) throw new IllegalArgumentException("Too short:
"+headers.length);
+ hashIdentifier = (short)(((headers[0] & 0xff) << 8) + (headers[1] &
0xff));
+ if(hashIdentifier != HASH_SHA256)
+ throw new SSKVerifyException("Hash not SHA-256");
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error(e);
+ }
+ // Now verify it
+ // Extract the signature
+ byte[] bufR = new byte[SIG_R_LENGTH];
+ byte[] bufS = new byte[SIG_S_LENGTH];
+ int x = 2;
+ if(x+SIG_R_LENGTH+SIG_S_LENGTH > headers.length)
+ throw new SSKVerifyException("Headers too short:
"+headers.length+" should be at least "+x+SIG_R_LENGTH+SIG_S_LENGTH);
+ System.arraycopy(headers, x, bufR, 0, SIG_R_LENGTH);
+ x+=SIG_R_LENGTH;
+ System.arraycopy(headers, x, bufS, 0, SIG_S_LENGTH);
+ x+=SIG_S_LENGTH;
+ // Compute the hash on the data
+ md.update(data);
+ byte[] dataHash = md.digest();
+ md.update(dataHash);
+ md.update(headers, x, headers.length - x);
+ byte[] overallHash = md.digest();
+ // Now verify it
+ NativeBigInteger r = new NativeBigInteger(1, bufR);
+ NativeBigInteger s = new NativeBigInteger(1, bufS);
+ if(!DSA.verify(pubKey, new DSASignature(r, s), new
NativeBigInteger(1, overallHash))) {
+ throw new SSKVerifyException("Signature verification
failed for node-level SSK");
+ }
+ headersOffset = x;
+ }
+
+ public Bucket decode(ClientKey key, BucketFactory factory, int
maxLength) throws KeyDecodeException, IOException {
+
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
Added: trunk/freenet/src/freenet/keys/SSKVerifyException.java
===================================================================
--- trunk/freenet/src/freenet/keys/SSKVerifyException.java 2005-12-16
15:48:43 UTC (rev 7722)
+++ trunk/freenet/src/freenet/keys/SSKVerifyException.java 2005-12-16
19:01:17 UTC (rev 7723)
@@ -0,0 +1,12 @@
+package freenet.keys;
+
+/**
+ * Thrown when an SSK fails to verify at the node level.
+ */
+public class SSKVerifyException extends Exception {
+
+ public SSKVerifyException(String string) {
+ super(string);
+ }
+
+}
Modified: trunk/freenet/src/freenet/store/DataStore.java
===================================================================
--- trunk/freenet/src/freenet/store/DataStore.java 2005-12-16 15:48:43 UTC
(rev 7722)
+++ trunk/freenet/src/freenet/store/DataStore.java 2005-12-16 19:01:17 UTC
(rev 7723)
@@ -27,8 +27,22 @@
public class DataStore extends Store {
- public static final String VERSION = "$Id: DataStore.java,v 1.5 2005/08/20
21:21:21 amphibian Exp $";
+ public class ATimeComparator implements Comparator {
+ public int compare(Object arg0, Object arg1) {
+ DataBlock db0 = (DataBlock) arg0;
+ DataBlock db1 = (DataBlock) arg1;
+ long a0 = db0.getLastAccessTime();
+ long a1 = db1.getLastAccessTime();
+ if(a0 < a1) return -1;
+ if(a0 > a1) return 1;
+ return 0;
+ }
+
+ }
+
+ public static final String VERSION = "$Id: DataStore.java,v 1.5
2005/08/20 21:21:21 amphibian Exp $";
+
private RandomAccessFile _index;
private final int blockSize;
@@ -55,6 +69,8 @@
_index.seek(0);
int recordNum = 0;
+ Vector v = new Vector();
+
try {
while (_index.getFilePointer() < _index.length()) {
@@ -65,14 +81,19 @@
getKeyMap().put(dataBlock.getKey(), dataBlock);
getRecordNumberList().add(recordNum, dataBlock);
-
- updateLastAccess(dataBlock);
+ v.add(dataBlock);
recordNum++;
}
} catch (EOFException e) {
// Chopped off in the middle of a key
Logger.normal(this, "Store index truncated");
return;
+ } finally {
+ DataBlock[] blocks = (DataBlock[]) v.toArray(new
DataBlock[v.size()]);
+ Arrays.sort(blocks, new ATimeComparator());
+ for(int i=0;i<blocks.length;i++) {
+ updateLastAccess(blocks[i]);
+ }
}
}