/**
* 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.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import TorJava.Common.Encoding;
import TorJava.Common.Encryption;
import TorJava.Common.StreamsAndHTTP;
import TorJava.Common.TorException;
/**
* class that represents the Service Descriptor
*
* @see http://tor.eff.org/cvs/tor/doc/tor-hidden-service.html
*
* @author Andriy
* @author Lexi
* @author Connell Gauld
*/
class ServiceDescriptor {
// public key
RSAPublicKeyStructure publicKey;
RSAKeyParameters privateKey;
byte[] signature;
int timestamp, numberOfIntroPoints, version;
String url;
// introduction points
HashSet<IntroductionPoint> introPoints;
byte[] bytesIntroPoints;
/**
* constructor for creating a service descriptor
*/
ServiceDescriptor(int version, RSAKeyParameters publicKey,
RSAKeyParameters privateKey,
HashSet<IntroductionPoint> given_introPoints) throws TorException {
if (version != 0)
throw new TorException("not implemented"); // FIXME: service
// descriptors of
// version != 0 are not
// supported, yet
this.version = version;
this.timestamp = (int) (System.currentTimeMillis() / 1000L);
this.publicKey = new RSAPublicKeyStructure(publicKey.getModulus(),
publicKey.getExponent());
this.privateKey = privateKey;
updateURL();
// store intro-points
introPoints = given_introPoints;
numberOfIntroPoints = given_introPoints.size();
byte[] temp = new byte[introPoints.size() * 100];
int temp_fill = 0;
Iterator<IntroductionPoint> i = introPoints.iterator();
while (i.hasNext()) {
byte[] s = i.next().getIdentityDigest().getBytes();
System.arraycopy(s, 0, temp, temp_fill, s.length);
temp_fill += s.length + 1;
}
this.bytesIntroPoints = new byte[temp_fill];
System.arraycopy(temp, 0, bytesIntroPoints, 0, temp_fill);
}
/**
* needs to be called, in case of service descriptor is self-generated and
* shall be called with toByteArray()
*/
void updateSignature() throws TorException {
signature = Encryption.signData(toByteArray(false), privateKey);
}
/**
* for sending the descriptor
*/
byte[] toByteArray() throws TorException {
return toByteArray(true);
}
/**
* for sending the descriptor
*/
private byte[] toByteArray(boolean withSignature) throws TorException {
// get PK
byte[] publicKey_bytes;
try {
publicKey_bytes = Encryption
.getPKCS1EncodingFromRSAPublicKey(publicKey);
} catch (Exception e) {
throw new TorException("ServiceDescriptor.toByteArray(): "
+ e.getMessage());
}
byte[] kl = Encoding.intToNByteArray(publicKey_bytes.length, 2);
byte[] ts = Encoding.intToNByteArray(timestamp, 4);
byte[] ni = Encoding.intToNByteArray(numberOfIntroPoints, 2);
if (numberOfIntroPoints < 1)
Logger
.logGeneral(Logger.WARNING,
"ServiceDescriptor.toByteArray(): strange data, possibly wrong?");
// concatenate all together
byte[] result;
if (withSignature) {
if (signature == null)
updateSignature();
result = new byte[2 + publicKey_bytes.length + 4 + 2
+ bytesIntroPoints.length + signature.length];
} else
result = new byte[2 + publicKey_bytes.length + 4 + 2
+ bytesIntroPoints.length];
System.arraycopy(kl, 0, result, 0, 2);
System.arraycopy(publicKey_bytes, 0, result, 2, publicKey_bytes.length);
System.arraycopy(ts, 0, result, 2 + publicKey_bytes.length, 4);
System.arraycopy(ni, 0, result, 6 + publicKey_bytes.length, 2);
System.arraycopy(bytesIntroPoints, 0, result,
8 + publicKey_bytes.length, bytesIntroPoints.length);
if (withSignature)
System.arraycopy(signature, 0, result, 8 + publicKey_bytes.length
+ bytesIntroPoints.length, signature.length);
// return result
return result;
}
/**
* constructor that is applied for data received from a dir-server. this one
* parses the data and sets all fields appropriately
*
*/
ServiceDescriptor(String url, byte[] sd, Directory dir) throws IOException,
TorException {
if (sd == null)
throw new IOException("ServiceDescriptor: given NULL-data");
int offset = 0;
if ((sd[0] == 255) && (sd[1] == 1)) {
offset = 2; // V1 descriptor
version = 1;
} else {
offset = 0; // V0 descriptor
version = 0;
}
int keyLength = Encoding.byteArrayToInt(sd, offset, offset + 2);
byte[] bytesKey = new byte[keyLength];
System.arraycopy(sd, offset + 2, bytesKey, 0, keyLength);
publicKey = Encryption.extractBinaryRSAKey(bytesKey);
timestamp = Encoding.byteArrayToInt(sd, keyLength + offset + 2, 4);
if (!checkTimeStampValidity())
throw new TorException("ServiceDescriptor: TimeStamp is not valid");
if (offset == 0) {
// proceed V0: parse introduction points
offset += keyLength + 6;
numberOfIntroPoints = Encoding.byteArrayToInt(sd, offset, 2);
offset += 2;
int offset_intro_start = offset;
introPoints = new HashSet<IntroductionPoint>(numberOfIntroPoints);
for (int i = 0; i < numberOfIntroPoints; ++i) {
String or = "";
while (sd[offset] != 0) {
or += (char) sd[offset];
offset++;
}
introPoints.add(new IntroductionPoint(or, dir));
offset++;
}
// save list of intro-points as they were
bytesIntroPoints = new byte[offset - offset_intro_start];
System.arraycopy(sd, offset_intro_start, bytesIntroPoints, 0,
offset - offset_intro_start);
} else {
// TODO proceed V1 as in 0.1.1.6-alpha
offset += keyLength + 8; // next 2 bytes PROTO, skip them at the
// point
int authNumber = sd[offset]; // NA number of auth mechanisms
// accepted
int i = 0;
while (i < authNumber) {
// AUTHT 2 octets
// AUTHL 1 octet
// AUTHD variable
i++;
}
numberOfIntroPoints = Encoding.byteArrayToInt(sd, offset, 2);
introPoints = new HashSet<IntroductionPoint>(numberOfIntroPoints);
i = 0;
while (i < numberOfIntroPoints) {
// ATYPE 1 octets
// ADDR 4 or 6 octets
// PORT 2 octets
// AUTHT 2 octets
// AUTHL 1 octet
// AUTHD variable
// ID 20 octets
// KLEN 2 octets
// KEY [KLEN octets]
i++;
}
}
// offset points to begin of signature
// now copy and verify the signature
byte[] input = new byte[offset];
signature = new byte[sd.length - offset];
System.arraycopy(sd, offset, signature, 0, sd.length - offset);
System.arraycopy(sd, 0, input, 0, offset);
// DEBUGING-OUTPUT-BEGIN
/*
* byte hash[] = Common.getHash(input); System.out.println("All
* Data:"+sd.length+" bytes\n"+Common.toHexString(sd));
* System.out.println("Hash :"+hash.length+"
* bytes\n"+Common.toHexString(hash));
* System.out.println("Signature:"+signature.length+"
* bytes\n"+Common.toHexString(signature)); System.out.println("Signed
* Data:"+input.length+" bytes\n"+Common.toHexString(input)); if (!
* Common.verifySignatureXXXX(signature, publicKey, input)) throw new
* TorException("ServiceDescriptor: signature verification failed");
*/
// DEBUGING-OUTPUT-END
if (!Encryption.verifySignature(signature, publicKey, input))
throw new TorException(
"ServiceDescriptor: signature verification failed");
// check base32-encoding of public-key to be equivalent to this.url
updateURL();
// if ( (url!=null) && (! this.url.equalsIgnoreCase(url)) )
// throw new TorException("ServiceDescriptor: some error parsing the
// service descriptor, received URL "+this.url+" instead of "+url);
}
/**
* loads a descriptor from the networks
*
* @param z
* the z-part of the address
*/
static ServiceDescriptor loadFromDirectory(String z, Tor tor)
throws IOException {
int i = 2; // Try twice
while (i > 0) {
// make a connection to the directory service
HashMap<String, HashMap<String, Object>> directoryServers = tor.config.trustedServers;
Iterator<String> it = directoryServers.keySet().iterator();
while (it.hasNext()) {
// fetch data for hidden service's rendezvous anonymously
String dirServerNick = (String) it.next();
HashMap<String, Object> dirServerEntry = tor.config.trustedServers
.get(dirServerNick);
String address = (String) dirServerEntry.get("ip");
int port = ((Integer) dirServerEntry.get("port")).intValue();
TCPStreamProperties sp = new TCPStreamProperties(address, port);
TCPStream stream = null;
Logger.logGeneral(Logger.INFO,
"ServiceDescriptor.loadFromDirectory: fetching service descriptor for "
+ z + " from " + dirServerNick + " (" + address
+ ":" + port + ")");
try {
stream = tor.connect(sp);
} catch (IOException e) {
Logger.logGeneral(Logger.WARNING,
"ServiceDescriptor.loadFromDirectory: unable to connect to directory server "
+ address + "(" + e.getMessage() + ")");
continue;
}
byte[] interm = StreamsAndHTTP.retrieveServiceDescriptor(
stream, z);
// Search for end of headers (CR, LF, CR, LF)
int dataStart = 0;
for (int m = 0; m < (interm.length - 3); m++) {
if ((interm[m] == 13) && (interm[m + 1] == 10)
&& (interm[m + 2] == 13) && (interm[m + 3] == 10)) {
dataStart = m + 4;
break;
}
}
// Separate data and headers
byte[] data = new byte[interm.length - dataStart];
System.arraycopy(interm, dataStart, data, 0, data.length);
byte[] headers = new byte[dataStart];
System.arraycopy(interm, 0, headers, 0, dataStart);
String str = new String(headers);
// System.out.println("After retrieve:" + str);
stream.close();
if (!str.startsWith("HTTP/")) {
Logger.logGeneral(Logger.ERROR,
"ServiceDescriptor.loadFromDirectory: directory request failed: no http!\n"
+ str);
} else if (str.indexOf(" 400") != -1) {
Logger.logGeneral(Logger.ERROR,
"ServiceDescriptor.loadFromDirectory: hidden service "
+ z + " directory request failed: 400");
} else if (str.indexOf(" 200") != -1) {
// str = Parsing.parseStringByRE(str, ".*?\n\n(.*)", "");
try {
return new ServiceDescriptor(z, data, tor.dir);
} catch (TorException e) {
Logger.logGeneral(Logger.WARNING,
"ServiceDescriptor.loadFromDirectory: problem parsing Service Descriptor for "
+ z);
continue;
}
}
}
--i;
}
;
Logger.logGeneral(Logger.WARNING,
"ServiceDescriptor.loadFromDirectory: unable to fetch service descriptor for "
+ z);
throw new IOException(
"ServiceDescriptor.loadFromDirectory: unable to fetch service descriptor for "
+ z);
}
private void updateURL() {
try {
// create hash of public key
byte[] hash = Encryption.getHash(Encryption
.getPKCS1EncodingFromRSAPublicKey(publicKey));
// take top 80-bits and convert to biginteger
byte[] h1 = new byte[10];
System.arraycopy(hash, 0, h1, 0, 10);
// return encoding
this.url = Encoding.toBase32(h1);
} catch (Exception e) {
Logger.logGeneral(Logger.ERROR, "ServiceDescriptor.updateURL(): "
+ e.getMessage());
e.printStackTrace();
this.url = null;
}
}
/**
* checks whether the timestamp is no older than 24h
*
* @param timestamp
* time stamp to check
*
*/
boolean checkTimeStampValidity() {
return (((int) (System.currentTimeMillis() / 1000)) - timestamp) < 86400;
}
/**
* returns the z-part of the url
*/
String getURL() {
return url;
}
}