/**
* 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;
}
}