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]);
+                       }
                }
        }



Reply via email to