freenet.keys.InsertableClientSSK.java Source code

Java tutorial

Introduction

Here is the source code for freenet.keys.InsertableClientSSK.java

Source

/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.keys;

import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
import org.bouncycastle.crypto.signers.DSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;

import java.io.IOException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.util.Arrays;

import freenet.crypt.DSAGroup;
import freenet.crypt.DSAPrivateKey;
import freenet.crypt.DSAPublicKey;
import freenet.crypt.Global;
import freenet.crypt.PCFBMode;
import freenet.crypt.RandomSource;
import freenet.crypt.SHA256;
import freenet.crypt.UnsupportedCipherException;
import freenet.crypt.Util;
import freenet.crypt.ciphers.Rijndael;
import freenet.keys.Key.Compressed;
import freenet.support.Logger;
import freenet.support.api.Bucket;
import freenet.support.compress.InvalidCompressionCodecException;
import freenet.support.math.MersenneTwister;

/** A ClientSSK that has a private key and therefore can be inserted. */
public class InsertableClientSSK extends ClientSSK {

    private static final long serialVersionUID = 1L;

    public final DSAPrivateKey privKey;

    private static boolean logMINOR;
    static {
        Logger.registerClass(InsertableClientSSK.class);
    }

    public InsertableClientSSK(String docName, byte[] pubKeyHash, DSAPublicKey pubKey, DSAPrivateKey privKey,
            byte[] cryptoKey, byte cryptoAlgorithm) throws MalformedURLException {
        super(docName, pubKeyHash, getExtraBytes(cryptoAlgorithm), pubKey, cryptoKey);
        if (pubKey == null)
            throw new NullPointerException();
        this.privKey = privKey;
    }

    protected InsertableClientSSK() {
        // For serialization.
        privKey = null;
    }

    public static InsertableClientSSK create(FreenetURI uri) throws MalformedURLException {
        if (uri.getKeyType().equalsIgnoreCase("KSK"))
            return ClientKSK.create(uri);

        if (uri.getRoutingKey() == null)
            throw new MalformedURLException("Insertable SSK URIs must have a private key!: " + uri);
        if (uri.getCryptoKey() == null)
            throw new MalformedURLException("Insertable SSK URIs must have a private key!: " + uri);

        byte keyType;

        byte[] extra = uri.getExtra();
        if (uri.getKeyType().equals("SSK")) {
            if (extra == null)
                throw new MalformedURLException("Inserting pre-1010 keys not supported");
            // Formatted exactly as ,extra on fetching
            if (extra.length < 5)
                throw new MalformedURLException("SSK private key ,extra too short");
            if (extra[1] != 1) {
                throw new MalformedURLException("SSK not a private key");
            }
            keyType = extra[2];
            if (keyType != Key.ALGO_AES_PCFB_256_SHA256)
                throw new MalformedURLException("Unrecognized crypto type in SSK private key");
        } else {
            throw new MalformedURLException("Not a valid SSK insert URI type: " + uri.getKeyType());
        }

        // Allow docName="" for SSKs. E.g. GenerateSSK returns these; we want to be consistent. 
        // However, we recommend that you not use this, especially not for a freesite, as 
        // SSK@blah,blah,blah//filename is confusing for clients, browsers etc.
        if (uri.getDocName() == null)
            throw new MalformedURLException("SSK URIs must have a document name (to avoid ambiguity)");
        DSAGroup g = Global.DSAgroupBigA;
        DSAPrivateKey privKey;
        try {
            privKey = new DSAPrivateKey(new BigInteger(1, uri.getRoutingKey()), g);
        } catch (IllegalArgumentException e) {
            // DSAPrivateKey is invalid
            Logger.error(InsertableClientSSK.class, "Caught " + e, e);
            throw new MalformedURLException("SSK private key (routing key) is invalid: " + e);
        }
        DSAPublicKey pubKey = new DSAPublicKey(g, privKey);
        byte[] pkHash = pubKey.asBytesHash();
        return new InsertableClientSSK(uri.getDocName(), pkHash, pubKey, privKey, uri.getCryptoKey(), keyType);
    }

    public ClientSSKBlock encode(Bucket sourceData, boolean asMetadata, boolean dontCompress,
            short alreadyCompressedCodec, long sourceLength, RandomSource r, String compressordescriptor,
            boolean pre1254) throws SSKEncodeException, IOException, InvalidCompressionCodecException {
        byte[] compressedData;
        short compressionAlgo;
        try {
            Compressed comp = Key.compress(sourceData, dontCompress, alreadyCompressedCodec, sourceLength,
                    ClientSSKBlock.MAX_DECOMPRESSED_DATA_LENGTH, SSKBlock.DATA_LENGTH, true, compressordescriptor,
                    pre1254);
            compressedData = comp.compressedData;
            compressionAlgo = comp.compressionAlgorithm;
        } catch (KeyEncodeException e) {
            throw new SSKEncodeException(e.getMessage(), e);
        }
        // Pad it
        MessageDigest md256 = SHA256.getMessageDigest();
        try {
            byte[] data;
            // First pad it
            if (compressedData.length != SSKBlock.DATA_LENGTH) {
                // Hash the data
                if (compressedData.length != 0)
                    md256.update(compressedData);
                byte[] digest = md256.digest();
                MersenneTwister mt = new MersenneTwister(digest);
                data = Arrays.copyOf(compressedData, SSKBlock.DATA_LENGTH);
                if (compressedData.length > data.length) {
                    throw new RuntimeException(
                            "compressedData.length = " + compressedData.length + " but data.length=" + data.length);
                }
                Util.randomBytes(mt, data, compressedData.length, SSKBlock.DATA_LENGTH - compressedData.length);
            } else {
                data = compressedData;
            }

            // Implicit hash of data
            byte[] origDataHash = md256.digest(data);

            Rijndael aes;
            try {
                aes = new Rijndael(256, 256);
            } catch (UnsupportedCipherException e) {
                throw new Error("256/256 Rijndael not supported!");
            }

            // Encrypt data. Data encryption key = H(plaintext data).

            aes.initialize(origDataHash);
            PCFBMode pcfb = PCFBMode.create(aes, origDataHash);

            pcfb.blockEncipher(data, 0, data.length);

            byte[] encryptedDataHash = md256.digest(data);

            // Create headers

            byte[] headers = new byte[SSKBlock.TOTAL_HEADERS_LENGTH];
            // First two bytes = hash ID
            int x = 0;
            headers[x++] = (byte) (KeyBlock.HASH_SHA256 >> 8);
            headers[x++] = (byte) (KeyBlock.HASH_SHA256);
            // Then crypto ID
            headers[x++] = (byte) (Key.ALGO_AES_PCFB_256_SHA256 >> 8);
            headers[x++] = Key.ALGO_AES_PCFB_256_SHA256;
            // Then E(H(docname))
            // Copy to headers
            System.arraycopy(ehDocname, 0, headers, x, ehDocname.length);
            x += ehDocname.length;
            // Now the encrypted headers
            byte[] encryptedHeaders = Arrays.copyOf(origDataHash, SSKBlock.ENCRYPTED_HEADERS_LENGTH);
            int y = origDataHash.length;
            short len = (short) compressedData.length;
            if (asMetadata)
                len |= 32768;
            encryptedHeaders[y++] = (byte) (len >> 8);
            encryptedHeaders[y++] = (byte) len;
            encryptedHeaders[y++] = (byte) (compressionAlgo >> 8);
            encryptedHeaders[y++] = (byte) compressionAlgo;
            if (encryptedHeaders.length != y)
                throw new IllegalStateException("Have more bytes to generate encoding SSK");
            aes.initialize(cryptoKey);
            pcfb.reset(ehDocname);
            pcfb.blockEncipher(encryptedHeaders, 0, encryptedHeaders.length);
            System.arraycopy(encryptedHeaders, 0, headers, x, encryptedHeaders.length);
            x += encryptedHeaders.length;
            // Generate implicit overall hash.
            md256.update(headers, 0, x);
            md256.update(encryptedDataHash);
            byte[] overallHash = md256.digest();
            // Now sign it
            DSASigner dsa = new DSASigner(new HMacDSAKCalculator(new SHA256Digest()));
            dsa.init(true, new DSAPrivateKeyParameters(privKey.getX(), Global.getDSAgroupBigAParameters()));
            BigInteger[] sig = dsa.generateSignature(Global.truncateHash(overallHash));
            // Pack R and S into 32 bytes each, and copy to headers.
            // Then create and return the ClientSSKBlock.
            byte[] rBuf = truncate(sig[0].toByteArray(), SSKBlock.SIG_R_LENGTH);
            byte[] sBuf = truncate(sig[1].toByteArray(), SSKBlock.SIG_S_LENGTH);
            System.arraycopy(rBuf, 0, headers, x, rBuf.length);
            x += rBuf.length;
            System.arraycopy(sBuf, 0, headers, x, sBuf.length);
            x += sBuf.length;
            if (x != SSKBlock.TOTAL_HEADERS_LENGTH)
                throw new IllegalStateException("Too long");
            try {
                return new ClientSSKBlock(data, headers, this, !logMINOR);
            } catch (SSKVerifyException e) {
                throw (AssertionError) new AssertionError("Impossible encoding error").initCause(e);
            }
        } finally {
            SHA256.returnMessageDigest(md256);
        }
    }

    private byte[] truncate(byte[] bs, int len) {
        if (bs.length == len)
            return bs;
        else if (bs.length < len) {
            byte[] buf = new byte[len];
            System.arraycopy(bs, 0, buf, len - bs.length, bs.length);
            return buf;
        } else { // if (bs.length > len) {
            for (int i = 0; i < (bs.length - len); i++) {
                if (bs[i] != 0)
                    throw new IllegalStateException("Cannot truncate");
            }
            return Arrays.copyOfRange(bs, bs.length - len, bs.length);
        }
    }

    public static InsertableClientSSK createRandom(RandomSource r, String docName) {
        byte[] ckey = new byte[CRYPTO_KEY_LENGTH];
        r.nextBytes(ckey);
        DSAGroup g = Global.DSAgroupBigA;
        DSAPrivateKey privKey = new DSAPrivateKey(g, r);
        DSAPublicKey pubKey = new DSAPublicKey(g, privKey);
        try {
            byte[] pkHash = SHA256.digest(pubKey.asBytes());
            return new InsertableClientSSK(docName, pkHash, pubKey, privKey, ckey, Key.ALGO_AES_PCFB_256_SHA256);
        } catch (MalformedURLException e) {
            throw new Error(e);
        }
    }

    public FreenetURI getInsertURI() {
        return new FreenetURI("SSK", docName, privKey.getX().toByteArray(), cryptoKey, getInsertExtraBytes());
    }

    private byte[] getInsertExtraBytes() {
        byte[] extra = getExtraBytes();
        extra[1] = 1; // insert
        return extra;
    }

    public DSAGroup getCryptoGroup() {
        return Global.DSAgroupBigA;
    }

}