package org.torproject.jtor.hiddenservice;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.torproject.jtor.TorClient;
import org.torproject.jtor.TorException;
import org.torproject.jtor.circuits.Circuit;
import org.torproject.jtor.circuits.CircuitManager;
import org.torproject.jtor.circuits.OpenStreamResponse;
import org.torproject.jtor.circuits.impl.CircuitManagerImpl;
import org.torproject.jtor.crypto.TorMessageDigest;
import org.torproject.jtor.crypto.TorPrivateKey;
import org.torproject.jtor.crypto.TorPublicKey;
import org.torproject.jtor.data.Base32;
import org.torproject.jtor.data.HexDigest;
import org.torproject.jtor.directory.Directory;
import org.torproject.jtor.directory.Router;
import org.torproject.jtor.directory.impl.HttpConnection;
import org.torproject.jtor.directory.impl.RouterImpl;
import com.sun.org.apache.xml.internal.security.utils.Base64;
/**
* Author: Kory Kirk
* ServiceDescriptor is the class that represents the V2 Service Descriptor used for publishing
* Hidden Services.
*/
public class HiddenServiceDescriptor {
/** The Constant PERMANENT_ID_SIZE. */
private final static int PERMANENT_ID_SIZE = 10;
/** The descriptor VERSION. */
private final static String VERSION = "2";
/** The descriptor data. */
private byte[] descriptorData;
/** The time in which the descriptor is no longer valid */
private int validUntil;
/** The descriptor string. */
private String descriptorString;
/** The permanent id. */
private byte[] permanentID;
/** The optional descriptor cookie. (for client authentication) */
private byte[] descriptorCookie;
/** The descriptor id. */
private byte[] descriptorID;
/** The secret id. */
private byte[] secretID;
/** The permanent key. */
private TorPublicKey permanentKey;
/** The private key */
private TorPrivateKey privateKey;
/** The time period. */
private long timePeriod;
/** The replica. */
private int replica = 0;
/** The date format. */
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/** a list of the rendezvous points */
private List<Router> rendPoints;
/**
* Instantiates a new service descriptor.
*
* @param permanentID
* the permanent id
*/
private HiddenServiceDescriptor(byte[] permanentID) {
rendPoints = new ArrayList<Router>();
this.permanentID = permanentID;
}
/**
* Checks for descriptor cookie.
*
* @return true, if successful
*/
public boolean hasDescriptorCookie(){
return (descriptorCookie!=null);
}
/*
*
*/
public static HiddenServiceDescriptor fetchServiceDescriptor(String onionAddress, Directory hiddenServiceDirectory, CircuitManager hsCircuit){
List<Router> hsDirectories = hiddenServiceDirectory.getHiddenServiceDirectories();
System.out.println("Sizes:"+hiddenServiceDirectory.getAllRouters().size());
HiddenServiceDescriptor returnDescriptor = null;
OpenStreamResponse hsDirectoryStreamResponse;
for (Router hsDirectoryRouter : hsDirectories) {
//open stream to router
try{
hsDirectoryStreamResponse = hsCircuit.openExitStreamTo(hsDirectoryRouter.getAddress(), hsDirectoryRouter.getDirectoryPort());
HttpConnection httpConnection = new HttpConnection(hsDirectoryRouter.getAddress().toString(), new BufferedReader(new InputStreamReader(hsDirectoryStreamResponse.getStream().getInputStream())), new BufferedWriter(new OutputStreamWriter(hsDirectoryStreamResponse.getStream().getOutputStream())));
System.out.println(httpConnection.getStatusMessage());
} catch (Exception e){
System.out.println(e);
}
}
return returnDescriptor;
}
private static void parseServerDescriptor(byte[] rawServiceDescriptor){
}
/**
* Generate descriptor id.
*/
public void generateDescriptorID(){
generateSecretID();
TorMessageDigest digest = new TorMessageDigest();
digest.update(permanentID);
digest.update(secretID);
descriptorID = digest.getDigestBytes();
}
/**
* Generate secret id.
*/
public void generateSecretID(){
generateTimePeriod();
TorMessageDigest digest = new TorMessageDigest();
digest.update(BigInteger.valueOf(timePeriod).toByteArray());
if (hasDescriptorCookie())
digest.update(descriptorCookie);
digest.update(BigInteger.valueOf(replica).toByteArray());
secretID = digest.getDigestBytes();
}
public void setPrivateKey(TorPrivateKey privKey) {
this.privateKey = privKey;
}
/**
* Generate time period.
*/
public void generateTimePeriod() {
long currentTime = (new Date()).getTime();
timePeriod = currentTime + ((int)(permanentID[0] * 86400 / 256)) / 86400;
}
/**
*
*
* Generate Service Descriptor.
*
* @param publicKey
* the public key
*
* @return a new Service Descriptor
*/
public static HiddenServiceDescriptor generateServiceDescriptor(TorPrivateKey privKey){
TorPublicKey publicKey = privKey.getPublicKey();
byte[] permanentID = new byte[PERMANENT_ID_SIZE];
System.arraycopy(publicKey.getFingerprint().getRawBytes(), 0, permanentID, 0, PERMANENT_ID_SIZE);
HiddenServiceDescriptor ret = new HiddenServiceDescriptor(permanentID);
ret.setPrivateKey(privKey);
ret.setPermanentKey(publicKey);
return ret;
}
public TorPublicKey getPermanentKey() {
return permanentKey;
}
public void setPermanentKey(TorPublicKey permanentKey) {
this.permanentKey = permanentKey;
}
/**
* generates the descriptorString before advertisement
*
*/
public void generateDescriptorString() throws TorException{
generateDescriptorID();
descriptorString = "rendezvous-service-descriptor " + formatDescriptorID() + "\n";
descriptorString += "version " + VERSION + "\n";
descriptorString += "permanent-key \n" + permanentKey.toPEMFormat() + "\n";
descriptorString += "secret-id-part " + formatSecretID() + "\n";
descriptorString += "publication-time " + getPublicationTime() + "\n";
//supported versions - not sure about this yet
descriptorString += "protocol-versions V2 \n";
descriptorString += "introduction-points\n";
descriptorString += "-----BEGIN MESSAGE-----\n";
/**all the introduction points base64 encoded if descriptor cookie is present, then list is encrypted with AES in CTR mode with a random
initialization vector of 128 bits that is written to
the beginning of the encrypted string, and the "descriptor-cookie" as
secret key of 128 bits length. **/
byte[] introPoints = null;
if (!hasDescriptorCookie())
introPoints = getIntroductionPointString().getBytes();
else {//encrypt it
byte[] ivBytes = getRandomKey();
byte[] keyBytes = getDescriptorCookie();
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
try{
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
ByteArrayInputStream bIn = new ByteArrayInputStream(getIntroductionPointString().getBytes());
CipherInputStream cIn = new CipherInputStream(bIn, cipher);
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
bOut.write(ivBytes);
int ch;
while ((ch = cIn.read()) >= 0) {
bOut.write(ch);
}
introPoints = bOut.toByteArray();
} catch (Exception e) {
throw new TorException(e);
}
}
descriptorString += Base64.encode(introPoints);
descriptorString += "\n-----END MESSAGE-----\n";
descriptorString += "signature \n";
//add signature string using private key.
}
public byte[] getRandomKey() {
byte[] retKey = new byte[16];
Random rndGen = new Random();
rndGen.nextBytes(retKey);
return retKey;
}
private String getIntroductionPointString() {
String retVal = "";
for (Router r : rendPoints){
retVal += "introduction-point " + r.getIdentityHash() +"\n";
retVal += "ip-address " + r.getAddress().toString() + "\n";
retVal += "onion-port " + r.getOnionPort() + "\n";
retVal += "onion-key \n" + r.getOnionKey().toPEMFormat() + "\n";
retVal += "service-key \n" + getPermanentKey().toPEMFormat() + "\n";
//retVal += "introduction-authentication " +
}
return retVal;
}
public void addRendPoint(Router r) {
rendPoints.add(r);
}
/*
* periodically changing identifier of 160 bits formatted as 32 base32
*/
private String formatSecretID() {
return Base32.base32Encode(secretID);
}
private String formatDescriptorID() {
return Base32.base32Encode(descriptorID);
}
public String getOnionAddress() {
return Base32.base32Encode(permanentID);
}
/**
* Encode descriptor.
*/
//getters
/**
*
*
* Gets the publication time.
* @return the publication time
*/
public String getPublicationTime() {
return dateFormat.format(new Date());
}
/**
* Gets the replica.
*
*
* @return the replica
*
*/
public int getReplica() {
return replica;
}
/**
* Gets the time period.
*
* @return the time period
*/
public long getTimePeriod() {
return timePeriod;
}
/**
* Gets the descriptor string.
*
* @return the descriptor string
*/
public String getDescriptorString() {
return descriptorString;
}
/**
* Gets the descriptor.
*
* @return the descriptor
*/
public byte[] getDescriptor() {
return descriptorData;
}
/**
* Gets the permanent id.
*
* @return the permanent id
*/
public byte[] getPermanentID() {
return permanentID;
}
/**
* Gets the descriptor cookie.
*
* @return the descriptor cookie
*/
public byte[] getDescriptorCookie() {
return descriptorCookie;
}
//setters
/**
* Sets the replica.
*
* @param replica
* the new replica
*/
public void setReplica(int replica) {
this.replica = replica;
}
/**
* Sets the permanent id.
*
* @param permenantID
* the new permanent id
*/
public void setPermanentID(byte[] permenantID) {
this.permanentID = permenantID;
}
/**
* Sets the descriptor cookie.
*
* @param descriptorCookie
* the new descriptor cookie
*/
public void setDescriptorCookie(byte[] descriptorCookie) {
this.descriptorCookie = descriptorCookie;
}
/**
* Gets the descriptor data.
*
* @return the descriptor data
*/
public byte[] getDescriptorData() {
return descriptorData;
}
/**
* Gets the descriptor id.
*
* @return the descriptor id
*/
public byte[] getDescriptorID() {
return descriptorID;
}
}