TorJava.Node.java Source code

Java tutorial

Introduction

Here is the source code for TorJava.Node.java

Source

/**
 * OnionCoffee - Anonymous Communication through TOR Network
 * Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
package TorJava;

import java.math.BigInteger;
import java.util.Random;

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAKeyParameters;

import TorJava.Common.AESCounterMode;
import TorJava.Common.Encoding;
import TorJava.Common.TorException;

/**
 * represents a server as part of a specific circuit. Stores the additional data
 * and contains all of the complete crypto-routines.
 * 
 * @author Lexi Pimenidis
 * @author Tobias Koelsch
 */
class Node {
    Server server;

    byte[] symmetric_key_for_create; // used to encrypt a part of the
                                     // diffie-hellman key-exchange

    BigInteger dh_private; // data for the diffie-hellman key-exchange

    BigInteger dh_x;

    byte[] dh_x_bytes;

    byte[] dh_y_bytes;

    byte[] kh; // the derived key data

    byte[] forward_digest; // digest for all data send to this node

    byte[] backward_digest; // digest for all data received from this node

    byte[] kf; // symmetric key for sending data

    byte[] kb; // symmetric key for receiving data

    AESCounterMode aes_encrypt;

    AESCounterMode aes_decrypt;

    SHA1Digest sha1_forward;

    SHA1Digest sha1_backward;

    // The SKIP 1024 bit modulus
    static final BigInteger dh_p = new BigInteger("00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE65381FFFFFFFFFFFFFFFF", 16);

    // The base used with the SKIP 1024 bit modulus
    static final BigInteger dh_g = new BigInteger("2");

    /** constructor for server-side.  */
    Node(Server init, byte[] dh_x_bytes) {
        if (init == null)
            throw new NullPointerException("can't init node on NULL server");
        // save a pointer to the server's data
        this.server = init;
        Random rnd = new Random();
        // do Diffie-Hellmann
        dh_x = new BigInteger(1, dh_x_bytes);
        dh_private = new BigInteger(dh_p.bitLength() - 1, rnd);
        BigInteger dh_xy = dh_x.modPow(dh_private, dh_p);
        byte[] dh_xy_bytes = BigIntegerTo128Bytes(dh_xy);

        // return dh_y-Bytes
        BigInteger dh_y = dh_g.modPow(dh_private, dh_p);
        dh_y_bytes = BigIntegerTo128Bytes(dh_y);
        // derive key-material
        SHA1Digest sha1 = new SHA1Digest();
        byte[] k = new byte[100];
        byte[] sha1_input = new byte[dh_xy_bytes.length + 1];
        System.arraycopy(dh_xy_bytes, 0, sha1_input, 0, dh_xy_bytes.length);
        for (int i = 0; i < 5; ++i) {
            sha1.reset();
            sha1_input[sha1_input.length - 1] = (byte) i;
            sha1.update(sha1_input, 0, sha1_input.length);
            sha1.doFinal(k, i * 20);
        }
        ;
        // DEBUGGING OUTPUT -- BEGIN
        Logger.logCrypto(Logger.VERBOSE, "Node.<init>: dh_x = \n" + Encoding.toHexString(dh_x_bytes, 100) + "\n"
                + "dh_y = \n" + Encoding.toHexString(dh_y_bytes, 100) + "\n" + "dh_xy = keymaterial:\n"
                + Encoding.toHexString(dh_xy_bytes, 100) + "\n" + "Key Data:\n" + Encoding.toHexString(k, 100));
        // DEBUGGING OUTPUT -- END

        // derived key info is correct - save to final destination
        // handshake
        kh = new byte[20];
        System.arraycopy(k, 0, kh, 0, 20);
        // forward digest
        forward_digest = new byte[20];
        System.arraycopy(k, 40, forward_digest, 0, 20);
        sha1_forward = new SHA1Digest();
        sha1_forward.update(forward_digest, 0, 20);
        // backward digest
        backward_digest = new byte[20];
        System.arraycopy(k, 20, backward_digest, 0, 20);
        sha1_backward = new SHA1Digest();
        sha1_backward.update(backward_digest, 0, 20);
        // secret key for sending data
        kf = new byte[16];
        System.arraycopy(k, 76, kf, 0, 16);
        aes_encrypt = new AESCounterMode(true, kf);
        // secret key for receiving data
        kb = new byte[16];
        System.arraycopy(k, 60, kb, 0, 16);
        aes_decrypt = new AESCounterMode(true, kb);
    }

    /** constructor for client-side */
    Node(Server init) {
        if (init == null)
            throw new NullPointerException("can't init node on NULL server");
        // save a pointer to the server's data
        this.server = init;
        Random rnd = new Random();

        // Diffie-Hellman: generate our secret
        dh_private = new BigInteger(dh_p.bitLength() - 1, rnd);
        // Diffie-Hellman: generate g^x
        dh_x = dh_g.modPow(dh_private, dh_p);
        dh_x_bytes = BigIntegerTo128Bytes(dh_x);

        // generate symmetric key for circuit creation
        symmetric_key_for_create = new byte[16];
        rnd.nextBytes(symmetric_key_for_create);
    }

    /**
     * encrypt data with asymmetric key. create asymmetricla encrypted data:<br>
     * <ul>
     * <li>OAEP padding [42 bytes] (RSA-encrypted)
     * <li>Symmetric key [16 bytes]                   FIXME: we assume that we ALWAYS need this 
     * <li>First part of data [70 bytes]
     * <li>Second part of data [x-70 bytes] (Symmetrically encrypted)
     * <ul>
     * encrypt and store in result
     * 
     * @param data
     *            to be encrypted, needs currently to be at least 70 bytes long
     * @return the first half of the key exchange, ready to be send to the other
     *         partner
     */
    byte[] asym_encrypt(byte[] data) throws TorException {
        if (data == null)
            throw new NullPointerException("can't encrypt NULL data");
        if (data.length < 70)
            throw new TorException("input array too short");

        try {
            int encrypted_bytes = 0;

            // init OAEP
            OAEPEncoding oaep = new OAEPEncoding(new RSAEngine());
            oaep.init(true,
                    new RSAKeyParameters(false, server.onionKey.getModulus(), server.onionKey.getPublicExponent()));
            // apply RSA+OAEP
            encrypted_bytes = oaep.getInputBlockSize();
            byte[] oaep_input = new byte[encrypted_bytes];
            System.arraycopy(data, 0, oaep_input, 0, encrypted_bytes);
            byte[] part1 = oaep.encodeBlock(oaep_input, 0, encrypted_bytes);

            // init AES
            AESCounterMode aes = new AESCounterMode(true, symmetric_key_for_create);
            // apply AES
            byte[] aes_input = new byte[data.length - encrypted_bytes];
            System.arraycopy(data, encrypted_bytes, aes_input, 0, aes_input.length);
            byte part2[] = aes.processStream(aes_input);

            // replace unencrypted data
            byte[] result = new byte[part1.length + part2.length];
            System.arraycopy(part1, 0, result, 0, part1.length);
            System.arraycopy(part2, 0, result, part1.length, part2.length);

            return result;
        } catch (InvalidCipherTextException e) {
            Logger.logCell(Logger.ERROR, "Node.asym_encrypt(): can't encrypt cipher text:" + e.getMessage());
            throw new TorException("InvalidCipherTextException:" + e.getMessage());
        }
    }

    /**
     * called after receiving created or extended cell: finished DH-key
     * exchange. Expects the first 148 bytes of the data array to be filled
     * with:<br>
     * <ul>
     * <li>128 bytes of DH-data (g^y)
     * <li>20 bytes of derivated key data (KH) (see chapter 4.2 of torspec)
     * </ul>
     * 
     * @param data
     *            expects the received second half of the DH-key exchange
     */
    void finish_dh(byte[] data) throws TorException {
        // calculate g^xy
        // - fix some undocument stuff: all numbers are 128-bytes only!
        // - add a leading zero to all numbers
        dh_y_bytes = new byte[128];
        System.arraycopy(data, 0, dh_y_bytes, 0, 128);
        BigInteger dh_y = new BigInteger(1, dh_y_bytes);
        BigInteger dh_xy = dh_y.modPow(dh_private, dh_p);
        byte[] dh_xy_bytes = BigIntegerTo128Bytes(dh_xy);

        // derivate key material
        SHA1Digest sha1 = new SHA1Digest();
        byte[] k = new byte[100];
        byte[] sha1_input = new byte[dh_xy_bytes.length + 1];
        System.arraycopy(dh_xy_bytes, 0, sha1_input, 0, dh_xy_bytes.length);
        for (int i = 0; i < 5; ++i) {
            sha1.reset();
            sha1_input[sha1_input.length - 1] = (byte) i;
            sha1.update(sha1_input, 0, sha1_input.length);
            sha1.doFinal(k, i * 20);
        }
        ;

        // DEBUGGING OUTPUT -- BEGIN
        Logger.logCrypto(Logger.VERBOSE,
                "Node.finish_dh: dh_x = \n" + Encoding.toHexString(dh_x_bytes, 100) + "\n" + "dh_y = \n"
                        + Encoding.toHexString(dh_y_bytes, 100) + "\n" + "dh_xy = keymaterial:\n"
                        + Encoding.toHexString(dh_xy_bytes, 100) + "\n" + "Key Data:\n"
                        + Encoding.toHexString(k, 100) + "\n" + "Data:\n" + Encoding.toHexString(data, 100));
        // DEBUGGING OUTPUT -- END

        // check if derived key data is equal to bytes 128-147 of data[]
        boolean equal = true;
        for (int i = 0; equal && (i < 20); ++i)
            equal = (k[i] == data[128 + i]);
        // is there some error in the key data?
        if (!equal)
            throw new TorException("derived key material is wrong!");

        // derived key info is correct - save to final destination
        // handshake
        kh = new byte[20];
        System.arraycopy(k, 0, kh, 0, 20);
        // forward digest
        forward_digest = new byte[20];
        System.arraycopy(k, 20, forward_digest, 0, 20);
        sha1_forward = new SHA1Digest();
        sha1_forward.update(forward_digest, 0, 20);
        // backward digest
        backward_digest = new byte[20];
        System.arraycopy(k, 40, backward_digest, 0, 20);
        sha1_backward = new SHA1Digest();
        sha1_backward.update(backward_digest, 0, 20);
        // secret key for sending data
        kf = new byte[16];
        System.arraycopy(k, 60, kf, 0, 16);
        aes_encrypt = new AESCounterMode(true, kf);
        // secret key for receiving data
        kb = new byte[16];
        System.arraycopy(k, 76, kb, 0, 16);
        aes_decrypt = new AESCounterMode(true, kb);
    }

    /**
     * calculate the forward digest
     * 
     * @param data
     * @return a four-byte array containing the digest
     */
    byte[] calc_forward_digest(byte[] data) {
        Logger.logCrypto(Logger.RAW_DATA, "Node.calc_forward_digest() on:\n" + Encoding.toHexString(data, 100));
        sha1_forward.update(data, 0, data.length);
        byte[] digest = new byte[20];
        SHA1Digest copyOldState = new SHA1Digest(sha1_forward); // ugly fix
                                                                // around
                                                                // bouncy-castle's
                                                                // behaviour on
                                                                // hashes
        sha1_forward.doFinal(digest, 0);
        sha1_forward = copyOldState;
        Logger.logCrypto(Logger.VERBOSE, " result:\n" + Encoding.toHexString(digest, 100));
        byte[] four_bytes = new byte[4];
        System.arraycopy(digest, 0, four_bytes, 0, 4);
        return four_bytes;
    }

    /**
     * calculate the backward digest
     * 
     * @param data
     * @return a four-byte array containing the digest
     */
    byte[] calc_backward_digest(byte[] data) {
        Logger.logCrypto(Logger.RAW_DATA, "Node.calc_backward_digest() on:\n" + Encoding.toHexString(data, 100));
        sha1_backward.update(data, 0, data.length);
        byte[] digest = new byte[20];
        SHA1Digest copyOldState = new SHA1Digest(sha1_backward); // ugly fix
                                                                 // around
                                                                 // bouncy-castle's
                                                                 // behaviour
                                                                 // on hashes
        sha1_backward.doFinal(digest, 0);
        sha1_backward = copyOldState;
        Logger.logCrypto(Logger.RAW_DATA, " result:\n" + Encoding.toHexString(digest, 100));
        byte[] four_bytes = new byte[4];
        System.arraycopy(digest, 0, four_bytes, 0, 4);
        return four_bytes;
    }

    /**
     * encrypt data with symmetric key
     * 
     * @param data
     *            is used for input and output.
     */
    void sym_encrypt(byte[] data) {
        Logger.logCrypto(Logger.VERBOSE, "Node.sym_encrypt for node " + server.nickname);
        //Logger.logCrypto(Logger.RAW_DATA, "Node.sym_encrypt in:\n"
        //       + Encoding.toHexString(data, 100));
        // encrypt data
        byte[] encrypted = aes_encrypt.processStream(data);
        // copy to output
        if (encrypted.length > data.length)
            System.arraycopy(encrypted, 0, data, 0, data.length);
        else
            System.arraycopy(encrypted, 0, data, 0, encrypted.length);
        // DEBUG: output
        Logger.logCrypto(Logger.RAW_DATA, "Node.sym_encrypt out:\n" + Encoding.toHexString(data, 100));
    }

    /**
     * decrypt data with symmetric key
     * 
     * @param data
     *            is used for input and output.
     */
    void sym_decrypt(byte[] data) {
        Logger.logCrypto(Logger.VERBOSE, "Node.sym_decrypt for node " + server.nickname);
        /*Logger.logCrypto(Logger.RAW_DATA, "Node.sym_decrypt in:\n" + Encoding.toHexString(data, 100)); */
        // decrypt data
        byte[] decrypted = aes_decrypt.processStream(data);
        // copy to output
        if (decrypted.length > data.length)
            System.arraycopy(decrypted, 0, data, 0, data.length);
        else
            System.arraycopy(decrypted, 0, data, 0, decrypted.length);
        // DEBUG: output
        /*Logger.logCrypto(Logger.RAW_DATA, "Node.sym_decrypt out:\n" + Encoding.toHexString(data, 100)); */
    }

    /** helper function to convert a bigInteger to a fixed-sized array for TOR-Usage */
    private byte[] BigIntegerTo128Bytes(BigInteger a) {
        byte[] temp = a.toByteArray();
        byte[] result = new byte[128];
        if (temp.length > 128)
            System.arraycopy(temp, temp.length - 128, result, 0, 128);
        else
            System.arraycopy(temp, 0, result, 128 - temp.length, temp.length);
        return result;
    }
}