great renaming (cont.)

This commit is contained in:
jrandom
2004-04-08 04:48:39 +00:00
committed by zzz
parent 77bd69c5e5
commit e40b94c875
125 changed files with 16881 additions and 0 deletions

Binary file not shown.

34
core/c/build.sh Normal file
View File

@ -0,0 +1,34 @@
#!/bin/sh
# linux settings:
CC="gcc"
ANT="ant"
JAVA="java"
COMPILEFLAGS="-fPIC -Wall"
LINKFLAGS="-shared -Wl,-soname,libjbigi.so"
INCLUDES="-Iinclude -I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
INCLUDELIBS="-lgmp"
STATICLIBS=""
LIBFILE="libjbigi.so"
# jrandom's mingw setup:
#INCLUDES="-Iinclude -Ic:/software/j2sdk1.4.2/include/win32/ -Ic:/software/j2sdk1.4.2/include/ -Ic:/dev/gmp-4.1.2/"
#LINKFLAGS="-shared -Wl,--kill-at"
#LIBFILE="jbigi.dll"
#INCLUDELIBS=""
#STATICLIBS="c:/dev/libgmp.a"
rm -f jbigi.o $LIBFILE
$CC -c $COMPILEFLAGS $INCLUDES src/jbigi.c
$CC $LINKFLAGS $INCLUDES $INCLUDELIBS -o $LIBFILE jbigi.o $STATICLIBS
echo "built, now testing"
(cd ../java/src/ ; $ANT )
LD_LIBRARY_PATH=. $JAVA -cp ../java/src/i2p.jar -DloggerConfigLocation=../java/src/logger.config net.i2p.util.NativeBigInteger
echo ""
echo ""
echo "test complete. please review the lines 'native run time:', 'java run time:', and 'native = '"

44
core/c/include/jbigi.h Normal file
View File

@ -0,0 +1,44 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class net_i2p_util_NativeBigInteger */
#ifndef _Included_net_i2p_util_NativeBigInteger
#define _Included_net_i2p_util_NativeBigInteger
#ifdef __cplusplus
extern "C" {
#endif
#undef net_i2p_util_NativeBigInteger_serialVersionUID
#define net_i2p_util_NativeBigInteger_serialVersionUID -8742448824652078965LL
#undef net_i2p_util_NativeBigInteger_LONG_MASK
#define net_i2p_util_NativeBigInteger_LONG_MASK 4294967295LL
/* Inaccessible static: bitsPerDigit */
/* Inaccessible static: SMALL_PRIME_PRODUCT */
#undef net_i2p_util_NativeBigInteger_MAX_CONSTANT
#define net_i2p_util_NativeBigInteger_MAX_CONSTANT 16L
/* Inaccessible static: posConst */
/* Inaccessible static: negConst */
/* Inaccessible static: ZERO */
/* Inaccessible static: ONE */
/* Inaccessible static: TWO */
/* Inaccessible static: bnExpModThreshTable */
/* Inaccessible static: trailingZeroTable */
/* Inaccessible static: zeros */
/* Inaccessible static: digitsPerLong */
/* Inaccessible static: longRadix */
/* Inaccessible static: digitsPerInt */
/* Inaccessible static: intRadix */
#undef net_i2p_util_NativeBigInteger_serialVersionUID
#define net_i2p_util_NativeBigInteger_serialVersionUID -8287574255936472291LL
/* Inaccessible static: _nativeOk */
/*
* Class: net_i2p_util_NativeBigInteger
* Method: nativeModPow
* Signature: ([B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_net_i2p_util_NativeBigInteger_nativeModPow
(JNIEnv *, jclass, jbyteArray, jbyteArray, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif

123
core/c/src/jbigi.c Normal file
View File

@ -0,0 +1,123 @@
#include <stdio.h>
#include <gmp.h>
#include "jbigi.h"
/********/
//function prototypes
//FIXME: should these go into jbigi.h? -- ughabugha
void convert_j2mp(JNIEnv* env, jbyteArray jvalue, mpz_t* mvalue);
void convert_mp2j(JNIEnv* env, mpz_t mvalue, jbyteArray* jvalue);
/********/
/*
* Class: net_i2p_util_NativeBigInteger
* Method: nativeModPow
* Signature: ([B[B[B)[B
*
* From the javadoc:
*
* calculate (base ^ exponent) % modulus.
* @param curVal big endian twos complement representation of the base (but it must be positive)
* @param exponent big endian twos complement representation of the exponent
* @param modulus big endian twos complement representation of the modulus
* @return big endian twos complement representation of (base ^ exponent) % modulus
*/
JNIEXPORT jbyteArray JNICALL Java_net_i2p_util_NativeBigInteger_nativeModPow
(JNIEnv* env, jclass cls, jbyteArray jbase, jbyteArray jexp, jbyteArray jmod) {
// convert base, exponent, modulus into the format libgmp understands
// call libgmp's modPow
// convert libgmp's result into a big endian twos complement number
mpz_t mbase;
mpz_t mexp;
mpz_t mmod;
//mpz_t mresult;
jbyteArray jresult;
convert_j2mp(env, jbase, &mbase);
convert_j2mp(env, jexp, &mexp);
convert_j2mp(env, jmod, &mmod);
//gmp_printf("mbase =%Zd\n", mbase);
//gmp_printf("mexp =%Zd\n", mexp);
//gmp_printf("mmod =%Zd\n", mmod);
mpz_powm(mmod, mbase, mexp, mmod);
//we use mod for the result because it is always at least as big
//gmp_printf("mresult=%Zd\n", mmod);
convert_mp2j(env, mmod, &jresult);
//convert_j2mp(env, jresult, &mresult);
//gmp_printf("", mpz_cmp(mmod, mresult) == 0 ? "true" : "false");
mpz_clear(mbase);
mpz_clear(mexp);
mpz_clear(mmod);
//mpz_clear(mresult);
return jresult;
}
/********/
/*
* Initializes the GMP value with enough preallocated size, and converts the
* Java value into the GMP value. The value that mvalue is pointint to
* should be uninitialized
*/
void convert_j2mp(JNIEnv* env, jbyteArray jvalue, mpz_t* mvalue)
{
jsize size;
jbyte* jbuffer;
size = (*env)->GetArrayLength(env, jvalue);
jbuffer = (*env)->GetByteArrayElements(env, jvalue, NULL);
mpz_init2(*mvalue, sizeof(jbyte) * 8 * size); //preallocate the size
/*
* void mpz_import (mpz_t rop, size_t count, int order, int size, int endian, size_t nails, const void *op)
* order = 1 - order can be 1 for most significant word first or -1 for least significant first.
* endian = 1 - Within each word endian can be 1 for most significant byte first, -1 for least significant first
* nails = 0 - The most significant nails bits of each word are skipped, this can be 0 to use the full words
*/
mpz_import(*mvalue, size, 1, sizeof(jbyte), 1, 0, (void*)jbuffer);
(*env)->ReleaseByteArrayElements(env, jvalue, jbuffer, JNI_ABORT);
}
/********/
/*
* Converts the GMP value into the Java value; Doesn't do anything else.
*/
void convert_mp2j(JNIEnv* env, mpz_t mvalue, jbyteArray* jvalue)
{
jsize size;
jbyte* buffer;
jboolean copy;
copy = JNI_FALSE;
size = (mpz_sizeinbase(mvalue, 2) + 7) / 8 + sizeof(jbyte); //+7 => ceil division
*jvalue = (*env)->NewByteArray(env, size);
buffer = (*env)->GetByteArrayElements(env, *jvalue, &copy);
buffer[0] = 0;
/*
* void *mpz_export (void *rop, size_t *count, int order, int size, int endian, size_t nails, mpz_t op)
*/
mpz_export((void*)&buffer[1], &size, 1, sizeof(jbyte), 1, 0, mvalue);
(*env)->ReleaseByteArrayElements(env, *jvalue, buffer, 0);
//mode has (supposedly) no effect if elems is not a copy of the elements in array
}
/********/

View File

@ -0,0 +1,11 @@
$Id$
the i2p/core/ module is the root of the I2P SDK, and
everything within it is released according to the
terms of the I2P license policy. For the I2P SDK,
that means everything contained within the i2p/core
module is released into the public domain unless
otherwise marked. Alternate licenses that may be
used include BSD (used by thecrypto's DSA, ElGamal,
and SHA256 implementations), Cryptix (used by cryptix's
AES implementation), and MIT.

30
core/java/build.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2p_sdk">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<!-- noop, since the core doesnt depend on anything -->
</target>
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac srcdir="./src:./test" debug="true" destdir="./build/obj" />
</target>
<target name="jar" depends="compile">
<jar destfile="./build/i2p.jar" basedir="./build/obj" includes="**/*.class" />
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc sourcepath="./src:./test" destdir="./build/javadoc" packagenames="*" use="true" splitindex="true" windowtitle="I2P SDK" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<!-- noop, since the core doesn't depend on anything -->
</target>
<target name="distclean" depends="clean">
<!-- noop, since the core doesn't depend on anything -->
</target>
</project>

View File

@ -0,0 +1,22 @@
package net.i2p;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
/**
* Expose a version string
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.33 $ $Date: 2004/04/04 13:40:34 $";
public final static String VERSION = "0.3.0.3";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);
System.out.println("ID: " + ID);
}
}

View File

@ -0,0 +1,48 @@
package net.i2p;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* Base class of I2P exceptions
*
* @author jrandom
*/
public class I2PException extends Exception {
private Throwable _source;
public I2PException() {
this(null, null);
}
public I2PException(String msg) {
this(msg, null);
}
public I2PException(String msg, Throwable source) {
super(msg);
_source = source;
}
public void printStackTrace() {
if (_source != null)
_source.printStackTrace();
super.printStackTrace();
}
public void printStackTrace(PrintStream ps) {
if (_source != null)
_source.printStackTrace(ps);
super.printStackTrace(ps);
}
public void printStackTrace(PrintWriter pw) {
if (_source != null)
_source.printStackTrace(pw);
super.printStackTrace(pw);
}
}

View File

@ -0,0 +1,338 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import net.i2p.I2PException;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.LogManager;
import net.i2p.util.Clock;
/**
* ATalk - anonymous talk, demonstrating a trivial I2P usage scenario.
* Run this class with no arguments for a manual.
*
* @author jrandom
*/
public class ATalk implements I2PSessionListener, Runnable {
/** logging hook - status messages are piped to this */
private final static Log _log = new Log(ATalk.class);
/** platform independent newline */
private final static String NL = System.getProperty("line.separator");
/** the current session */
private I2PSession _session;
/** who am i */
private Destination _myDestination;
/** who are you? */
private Destination _peerDestination;
/** location of my secret key file */
private String _myKeyFile;
/** location of their public key */
private String _theirDestinationFile;
/** where the application reads input from. currently set to standard input */
private BufferedReader _in;
/** where the application sends output to. currently set to standard output */
private BufferedWriter _out;
/** string that messages must begin with to be treated as files */
private final static String FILE_COMMAND = ".file: ";
/** the, erm, manual */
private final static String MANUAL =
"ATalk: Anonymous Talk, a demo program for the Invisible Internet Project SDK" + NL +
"To generate a new destination:" + NL +
"\tATalk [fileToSavePrivateKeyIn] [fileToSavePublicKeyIn]" + NL +
"To talk to another destination:" + NL +
"\tATalk [myPrivateKeyFile] [peerPublicKey] [shouldLogToScreen]" + NL +
"shouldLogToScreen is 'true' or 'false', depending on whether you want log info on the screen" + NL +
"When talking to another destination, messages are sent after you hit return" + NL +
"To send a file, send a message saying:" + NL +
"\t" + FILE_COMMAND + "[filenameToSend]" + NL +
"The peer will then recieve the file and be notified of where it has been saved" + NL +
"To end the talk session, enter a period on a line by itself and hit return" + NL;
public final static String PROP_CONFIG_LOCATION = "configFile";
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
/** Construct the talk engine, but don't connect yet */
public ATalk(String myKeyFile, String theirDestFile) {
_myKeyFile = myKeyFile;
_theirDestinationFile = theirDestFile;
}
/** Actually start up the connection to the I2P network.
* Successful connect does not mean the peer is online or reachable.
*
* @throws IOException if there is a problem reading in the keys from the files specified
* @throws DataFormatException if the key files are not in the valid format
* @throws I2PSessionException if there is a problem contacting the I2P router
*/
public void connect() throws IOException, I2PSessionException, DataFormatException {
I2PClient client = I2PClientFactory.createClient();
File myFile = new File(_myKeyFile);
Properties props = new Properties();
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "atalk.config");
try {
props.load(new FileInputStream(configLocation));
} catch (FileNotFoundException fnfe) {
_log.warn("Unable to load up the ATalk config file " + configLocation);
}
// Provide any router or client API configuration here.
if (!props.containsKey(I2PClient.PROP_TCP_HOST))
props.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
if (!props.containsKey(I2PClient.PROP_TCP_PORT))
props.setProperty(I2PClient.PROP_TCP_PORT, "7654");
if (!props.containsKey(I2PClient.PROP_RELIABILITY))
props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
_session = client.createSession(new FileInputStream(myFile), props);
_session.setSessionListener(this);
_session.connect();
File peerDestFile = new File(_theirDestinationFile);
_peerDestination = new Destination();
_peerDestination.readBytes(new FileInputStream(peerDestFile));
return;
}
/** Actual bulk processing of the application, reading in user input,
* sending messages, and displaying results. When this function exits, the
* application is complete.
*
*/
public void run() {
try {
connect();
_in = new BufferedReader(new InputStreamReader(System.in));
_out = new BufferedWriter(new OutputStreamWriter(System.out));
_out.write("Starting up anonymous talk session"+NL);
while (true) {
String line = _in.readLine();
if ( (line == null) || (line.trim().length() <= 0) )
continue;
if (".".equals(line)) {
boolean ok = _session.sendMessage(_peerDestination, ("Peer disconnected at " + now()).getBytes());
// ignore ok, we're closing
break;
}
if (line.startsWith(FILE_COMMAND) && (line.trim().length() > FILE_COMMAND.length())) {
try {
String file = line.substring(FILE_COMMAND.length());
boolean sent = sendFile(file);
if (!sent) {
_out.write("Failed sending the file: " + file+NL);
}
} catch (IOException ioe) {
_out.write("Error sending the file: " + ioe.getMessage()+NL);
_log.error("Error sending the file", ioe);
}
} else {
boolean ok = _session.sendMessage(_peerDestination, ("[" + now() + "] " + line).getBytes());
if (!ok) {
_out.write("Failed sending message. Peer disconnected?" + NL);
}
}
}
} catch (IOException ioe) {
_log.error("Error running", ioe);
} catch (I2PSessionException ise) {
_log.error("Error communicating", ise);
} catch (DataFormatException dfe) {
_log.error("Peer destination file is not valid", dfe);
} finally {
try {
_log.debug("Exiting anonymous talk session");
if (_out != null)
_out.write("Exiting anonymous talk session");
} catch (IOException ioe) {
// ignored
}
if (_session != null) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {
// ignored
}
}
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
}
}
private String now() {
Date now = new Date(Clock.getInstance().now());
return _fmt.format(now);
}
/** Send the given file to the current peer. This works by sending a message
* saying ".file: filename\nbodyOfFile", where filename is the name of the file
* (which the recipient will be shown), and the bodyOfFile is the set of raw
* bytes in the file.
*
* @throws IOException if the file could not be found or read
* @return false if the file could not be sent to the peer
*/
private boolean sendFile(String filename) throws IOException, I2PSessionException {
_log.debug("Sending file [" + filename + "]");
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
baos.write((FILE_COMMAND + filename+"\n").getBytes());
FileInputStream fin = new FileInputStream(filename);
byte buf[] = new byte[4096];
try {
while (true) {
int len = fin.read(buf);
if (len == -1)
break;
baos.write(buf, 0, len);
}
} catch (IOException ioe) {
_log.debug("Failed reading the file", ioe);
return false;
}
baos.close();
byte val[] = baos.toByteArray();
_log.debug("Sending " + filename + " with a full payload of " + val.length);
try {
boolean rv = _session.sendMessage(_peerDestination, val);
_log.debug("Sending " + filename + " complete: rv = " + rv);
return rv;
} catch (Throwable t) {
_log.error("Error sending file", t);
return false;
}
}
/** I2PSessionListener.messageAvailable requires this method to be called whenever
* I2P wants to tell the session that a message is available. ATalk always grabs
* the message immediately and either processes it as a "send file" command (passing
* it off to {@link #handleRecieveFile handleRecieveFile}) or simply displays the
* message to the user.
*
*/
public void messageAvailable(I2PSession session, int msgId, long size) {
_log.debug("Message available: id = " + msgId + " size = " + size);
try {
byte msg[] = session.receiveMessage(msgId);
// inefficient way to just read the first line of text, but its easy
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msg)));
String line = reader.readLine();
if (line.startsWith(FILE_COMMAND)) {
handleRecieveFile(line, msg);
} else {
// not a file command, so just plop 'er out on the screen
_out.write(now() + " --> " + new String(msg));
_out.write(NL);
_out.flush();
}
} catch (I2PSessionException ise) {
_log.error("Error fetching available message", ise);
} catch (IOException ioe) {
_log.error("Error writing out the message", ioe);
}
}
/** React to a file being sent our way from the peer via {@link #sendFile sendFile}
* by saving the file to a temporary location and displaying where, what, and how large
* it is.
*
* @param firstline the first line of the message that, according to the sendFile
* implementation, contains the command and the filename that it was stored
* at on the peer's computer
* @param msg the entire message recieved, including the firstline
*/
private void handleRecieveFile(String firstline, byte msg[]) throws IOException {
_log.debug("handleRecieveFile called");
File f = File.createTempFile("recieve", ".dat", new File("."));
FileOutputStream fos = new FileOutputStream(f);
int lineLen = firstline.getBytes().length+"\n".getBytes().length;
int lenToCopy = msg.length - lineLen;
byte buf[] = new byte[lenToCopy];
System.arraycopy(msg, lineLen, buf, 0, lenToCopy);
fos.write(buf);
fos.close();
String name = firstline.substring(FILE_COMMAND.length());
_out.write("Recieved a file called [" + name + "] of size [" + lenToCopy + "] bytes, saved as [" + f.getAbsolutePath() + "]" + NL);
_out.flush();
}
/** driver */
public static void main(String args[]) {
if (args.length == 2) {
String myKeyFile = args[0];
String myDestinationFile = args[1];
boolean success = generateKeys(myKeyFile, myDestinationFile);
if (success)
_log.debug("Keys generated (private key file: " + myKeyFile + " destination file: " + myDestinationFile + ")");
else
_log.debug("Keys generation failed");
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
} else if (args.length == 3) {
_log.debug("Starting chat");
String myKeyfile = args[0];
String peerDestFile = args[1];
String shouldLog = args[2];
if (Boolean.TRUE.toString().equalsIgnoreCase(shouldLog))
LogManager.getInstance().setDisplayOnScreen(true);
else
LogManager.getInstance().setDisplayOnScreen(false);
String logFile = args[2];
Thread talkThread = new I2PThread(new ATalk(myKeyfile, peerDestFile));
talkThread.start();
} else {
System.out.println(MANUAL);
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
System.exit(-1);
}
}
/** Generate a new Destination, saving that destination and the associated
* private keys in the privKeyFile, and also saving the destination without
* any private keys to destinationFile.
*
* @param privKeyFile private key file, including the destination and the various
* private keys, as defined by {@link I2PClient#createDestination I2PClient.createDestination}
* @param destinationFile file in which the Destination is serialized in
*/
private static boolean generateKeys(String privKeyFile, String destinationFile) {
try {
Destination d = I2PClientFactory.createClient().createDestination(new FileOutputStream(privKeyFile));
FileOutputStream fos = new FileOutputStream(destinationFile);
d.writeBytes(fos);
fos.flush();
fos.close();
return true;
} catch (IOException ioe) {
_log.error("Error generating keys", ioe);
} catch (I2PException ipe) {
_log.error("Error generating keys", ipe);
}
return false;
}
/** required by {@link I2PSessionListener I2PSessionListener} to notify of disconnect */
public void disconnected(I2PSession session) { _log.debug("Disconnected"); }
/** required by {@link I2PSessionListener I2PSessionListener} to notify of error */
public void errorOccurred(I2PSession session, String message, Throwable error) { _log.debug("Error occurred: " + message, error); }
/** required by {@link I2PSessionListener I2PSessionListener} to notify of abuse */
public void reportAbuse(I2PSession session, int severity) { _log.debug("Abuse reported of severity " + severity); }
}

View File

@ -0,0 +1,375 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
import net.i2p.util.Clock;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.data.i2cp.SendMessageMessage;
import net.i2p.data.i2cp.CreateSessionMessage;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.SessionConfig;
import net.i2p.data.Destination;
import net.i2p.data.Payload;
import net.i2p.data.TunnelId;
import net.i2p.data.RouterIdentity;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.Certificate;
import net.i2p.crypto.KeyGenerator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Date;
import java.net.Socket;
import java.io.OutputStream;
import java.io.IOException;
/**
* Run the server side of a connection as part of the TestServer. This class
* actually manages the state of that system too, but this is a very, very, very
* rudimentary implementation. And not a very clean one at that.
*
* @author jrandom
*/
class ConnectionRunner implements I2CPMessageReader.I2CPMessageEventListener {
private final static Log _log = new Log(ConnectionRunner.class);
/**
* static mapping of Destination to ConnectionRunner, allowing connections to pass
* messages to each other
*/
private static Map _connections = Collections.synchronizedMap(new HashMap());
/**
* static mapping of MessageId to Payload, storing messages for retrieval
*
*/
private static Map _messages = Collections.synchronizedMap(new HashMap());
/** socket for this particular peer connection */
private Socket _socket;
/**
* output stream of the socket that I2CP messages bound to the client
* should be written to
*/
private OutputStream _out;
/** session ID of the current client */
private SessionId _sessionId;
/** next available session id */
private static int _id = 0;
/** next available message id */
private static int _messageId = 0;
private SessionConfig _config;
private Object _sessionIdLock = new Object();
private Object _messageIdLock = new Object();
// this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
protected int getNextSessionId() { synchronized (_sessionIdLock) { int id = (++_id)%32767; _id = id; return id; } }
// this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
protected int getNextMessageId() { synchronized (_messageIdLock) { int id = (++_messageId)%32767; _messageId = id; return id; } }
protected SessionId getSessionId() { return _sessionId; }
protected ConnectionRunner getRunner(Destination dest) {
return (ConnectionRunner)_connections.get(dest);
}
protected Set getRunnerDestinations() {
return new HashSet(_connections.keySet());
}
/**
* Create a new runner against the given socket
*
*/
public ConnectionRunner(Socket socket) {
_socket = socket;
_config = null;
}
/**
* Actually run the connection - listen for I2CP messages and respond. This
* is the main driver for this class, though it gets all its meat from the
* {@link net.invisiblenet.i2p.data.i2cp.I2CPMessageReader I2CPMessageReader}
*
*/
public void doYourThing() throws IOException {
I2CPMessageReader reader = new I2CPMessageReader(_socket.getInputStream(), this);
_out = _socket.getOutputStream();
reader.startReading();
}
/**
* Recieve notifiation that the peer disconnected
*/
public void disconnected(I2CPMessageReader reader) {
_log.info("Disconnected");
}
/**
* Handle an incoming message and dispatch it to the appropriate handler
*
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
_log.info("Message recieved: \n" + message);
switch (message.getType()) {
case CreateSessionMessage.MESSAGE_TYPE:
handleCreateSession(reader, (CreateSessionMessage)message);
break;
case SendMessageMessage.MESSAGE_TYPE:
handleSendMessage(reader, (SendMessageMessage)message);
break;
case ReceiveMessageBeginMessage.MESSAGE_TYPE:
handleReceiveBegin(reader, (ReceiveMessageBeginMessage)message);
break;
case ReceiveMessageEndMessage.MESSAGE_TYPE:
handleReceiveEnd(reader, (ReceiveMessageEndMessage)message);
break;
}
}
/**
* Handle a CreateSessionMessage
*
*/
protected void handleCreateSession(I2CPMessageReader reader, CreateSessionMessage message) {
if (message.getSessionConfig().verifySignature()) {
_log.debug("Signature verified correctly on create session message");
} else {
_log.error("Signature verification *FAILED* on a create session message. Hijack attempt?");
DisconnectMessage msg = new DisconnectMessage();
msg.setReason("Invalid signature on CreateSessionMessage");
try {
doSend(msg);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the disconnect message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the disconnect message", ioe);
}
return;
}
SessionStatusMessage msg = new SessionStatusMessage();
SessionId id = new SessionId();
id.setSessionId(getNextSessionId()); // should be mod 65535, but UnsignedInteger isn't fixed yet. FIXME.
_sessionId = id;
msg.setSessionId(id);
msg.setStatus(SessionStatusMessage.STATUS_CREATED);
try {
doSend(msg);
_connections.put(message.getSessionConfig().getDestination(), this);
_config = message.getSessionConfig();
sessionCreated();
} catch (I2CPMessageException ime) {
_log.error("Error writing out the session status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the session status message", ioe);
}
// lets also request a new fake lease
RequestLeaseSetMessage rlsm = new RequestLeaseSetMessage();
rlsm.setEndDate(new Date(Clock.getInstance().now() + 60*60*1000));
rlsm.setSessionId(id);
RouterIdentity ri = new RouterIdentity();
Object rikeys[] = KeyGenerator.getInstance().generatePKIKeypair();
Object riSigningkeys[] = KeyGenerator.getInstance().generateSigningKeypair();
ri.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
ri.setPublicKey((PublicKey)rikeys[0]);
ri.setSigningPublicKey((SigningPublicKey)riSigningkeys[0]);
TunnelId tunnel = new TunnelId();
tunnel.setTunnelId(42);
rlsm.addEndpoint(ri, tunnel);
try {
doSend(rlsm);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the request for a lease set", ime);
} catch (IOException ioe) {
_log.error("Error writing out the request for a lease set", ioe);
}
}
protected void sessionCreated() { }
protected SessionConfig getConfig() { return _config; }
/**
* Handle a SendMessageMessage
*
*/
protected void handleSendMessage(I2CPMessageReader reader, SendMessageMessage message) {
_log.debug("handleSendMessage called");
Payload payload = message.getPayload();
Destination dest = message.getDestination();
MessageId id = new MessageId();
id.setMessageId(getNextMessageId());
_log.debug("** Recieving message [" + id.getMessageId() + "] with payload: " + "[" + payload + "]");
_messages.put(id, payload);
MessageStatusMessage status = new MessageStatusMessage();
status.setMessageId(id);
status.setSessionId(message.getSessionId());
status.setSize(0L);
status.setNonce(message.getNonce());
status.setStatus(MessageStatusMessage.STATUS_SEND_ACCEPTED);
try {
doSend(status);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the message status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the message status message", ioe);
}
distributeMessageToPeer(status, dest, id);
}
/**
* distribute the message to the destination, passing on the appropriate status
* messages to the sender of the SendMessageMessage
*
*/
private void distributeMessageToPeer(MessageStatusMessage status, Destination dest, MessageId id) {
ConnectionRunner runner = (ConnectionRunner)_connections.get(dest);
if (runner == null) {
distributeNonLocal(status, dest, id);
} else {
distributeLocal(runner, status, dest, id);
}
_log.debug("Done handling send message");
}
protected void distributeLocal(ConnectionRunner runner, MessageStatusMessage status, Destination dest, MessageId id) {
if (runner.messageAvailable(id, 0L)) {
status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
status.setNonce(2);
try {
doSend(status);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the success status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the success status message", ioe);
}
_log.debug("Guaranteed success with the status message sent");
} else {
status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE);
try {
doSend(status);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the failure status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the failure status message", ioe);
}
_log.debug("Guaranteed failure since messageAvailable failed");
}
}
protected void distributeNonLocal(MessageStatusMessage status, Destination dest, MessageId id) {
status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE);
try {
doSend(status);
} catch (I2CPMessageException ime) {
_log.error("Error writing out the failure status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the failure status message", ioe);
}
_log.debug("Guaranteed failure!");
}
/**
* The client asked for a message, so we send it to them. This currently
* does not do any security checking (like making sure they're the one to
* whom the message ID is destined, but its encrypted, so why not...
* (bad attitude, I know. consider this a bug to be fixed)
*
*/
public void handleReceiveBegin(I2CPMessageReader reader, ReceiveMessageBeginMessage message) {
_log.debug("Handling recieve begin: id = " + message.getMessageId());
MessagePayloadMessage msg = new MessagePayloadMessage();
msg.setMessageId(message.getMessageId());
msg.setSessionId(_sessionId);
Payload payload = (Payload)_messages.get(message.getMessageId());
if (payload == null) {
_log.error("Payload for message id [" + message.getMessageId() + "] is null! Unknown message id?", new Exception("Error, null payload"));
StringBuffer buf = new StringBuffer();
for (Iterator iter = _messages.keySet().iterator(); iter.hasNext(); ) {
buf.append("messageId: ").append(iter.next()).append(", ");
}
_log.error("Known message IDs: " + buf.toString());
return;
}
msg.setPayload(payload);
try {
doSend(msg);
} catch (IOException ioe) {
_log.error("Error delivering the payload", ioe);
} catch (I2CPMessageException ime) {
_log.error("Error delivering the payload", ime);
}
}
/**
* The client told us that the message has been recieved completely. This currently
* does not do any security checking prior to removing the message from the
* pending queue, though it should.
*
*/
public void handleReceiveEnd(I2CPMessageReader reader, ReceiveMessageEndMessage message) {
_messages.remove(message.getMessageId());
}
/**
* Deliver notification to the client that the given message is available.
* This is called from the ConnectionRunner the message was sent from.
*
*/
public boolean messageAvailable(MessageId id, long size) {
MessageStatusMessage msg = new MessageStatusMessage();
msg.setMessageId(id);
msg.setSessionId(_sessionId);
msg.setSize(size);
msg.setNonce(1);
msg.setStatus(MessageStatusMessage.STATUS_AVAILABLE);
try {
doSend(msg);
return true;
} catch (I2CPMessageException ime) {
_log.error("Error writing out the message status message", ime);
} catch (IOException ioe) {
_log.error("Error writing out the message status message", ioe);
}
return false;
}
/**
* Handle notifiation that there was an error
*
*/
public void readError(I2CPMessageReader reader, Exception error) {
_log.info("Error occurred", error);
}
private Object _sendLock = new Object();
protected void doSend(I2CPMessage msg) throws I2CPMessageException, IOException {
synchronized (_sendLock) {
msg.writeMessage(_out);
_out.flush();
}
}
}

View File

@ -0,0 +1,26 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.*;
/**
* Handle I2CP disconnect messages from the router
*
* @author jrandom
*/
class DisconnectMessageHandler extends HandlerImpl {
public DisconnectMessageHandler() {
super(DisconnectMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
session.destroySession(false);
}
}

View File

@ -0,0 +1,26 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
/**
* Base class for handling I2CP messages
*
* @author jrandom
*/
abstract class HandlerImpl implements I2CPMessageHandler {
protected Log _log;
private int _type;
public HandlerImpl(int type) {
_type = type;
_log = new Log(getClass());
}
public int getType() { return _type; }
}

View File

@ -0,0 +1,21 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.I2CPMessage;
/**
* Define a way to handle a particular type of message
*
* @author jrandom
*/
interface I2CPMessageHandler {
public int getType();
public void handleMessage(I2CPMessage message, I2PSessionImpl session);
}

View File

@ -0,0 +1,153 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.HashSet;
import java.util.Set;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.Payload;
import net.i2p.data.PrivateKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2cp.AbuseReason;
import net.i2p.data.i2cp.AbuseSeverity;
import net.i2p.data.i2cp.CreateLeaseSetMessage;
import net.i2p.data.i2cp.CreateSessionMessage;
import net.i2p.data.i2cp.DestroySessionMessage;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.ReportAbuseMessage;
import net.i2p.data.i2cp.SendMessageMessage;
import net.i2p.data.i2cp.SessionConfig;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* Produce the various messages the session needs to send to the router.
*
* @author jrandom
*/
class I2CPMessageProducer {
private final static Log _log = new Log(I2CPMessageProducer.class);
private final static RandomSource _rand = RandomSource.getInstance();
/**
* Send all the messages that a client needs to send to a router to establish
* a new session.
*/
public void connect(I2PSessionImpl session) throws I2PSessionException {
CreateSessionMessage msg = new CreateSessionMessage();
SessionConfig cfg = new SessionConfig();
cfg.setDestination(session.getMyDestination());
cfg.setOptions(session.getOptions());
try {
cfg.signSessionConfig(session.getPrivateKey());
} catch (DataFormatException dfe) {
throw new I2PSessionException("Unable to sign the session config", dfe);
}
msg.setSessionConfig(cfg);
session.sendMessage(msg);
}
/**
* Send messages to the router destroying the session and disconnecting
*
*/
public void disconnect(I2PSessionImpl session) throws I2PSessionException {
DestroySessionMessage dmsg = new DestroySessionMessage();
dmsg.setSessionId(session.getSessionId());
session.sendMessage(dmsg);
// use DisconnectMessage only if we fail and drop connection...
// todo: update the code to fire off DisconnectMessage on socket error
//DisconnectMessage msg = new DisconnectMessage();
//msg.setReason("Destroy called");
//session.sendMessage(msg);
}
/**
* Package up and send the payload to the router for delivery
*
*/
public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag, SessionKey key, Set tags, SessionKey newKey) throws I2PSessionException {
SendMessageMessage msg = new SendMessageMessage();
msg.setDestination(dest);
msg.setSessionId(session.getSessionId());
msg.setNonce(nonce);
Payload data = createPayload(dest, payload, tag, key, tags, newKey);
msg.setPayload(data);
session.sendMessage(msg);
}
/**
* Create a new signed payload and send it off to the destination
*
*/
private Payload createPayload(Destination dest, byte[] payload, SessionTag tag, SessionKey key, Set tags, SessionKey newKey) throws I2PSessionException {
if (dest == null)
throw new I2PSessionException("No destination specified");
if (payload == null)
throw new I2PSessionException("No payload specified");
Payload data = new Payload();
// randomize padding
int size = payload.length + RandomSource.getInstance().nextInt(1024);
byte encr[] = ElGamalAESEngine.encrypt(payload, dest.getPublicKey(), key, tags, tag, newKey, size);
// yes, in an intelligent component, newTags would be queued for confirmation along with key, and
// generateNewTags would only generate tags if necessary
data.setEncryptedData(encr);
_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: " + data.calculateHash());
return data;
}
private static Set generateNewTags() {
Set tags = new HashSet();
for (int i = 0; i < 10; i++) {
byte tag[] = new byte[SessionTag.BYTE_LENGTH];
RandomSource.getInstance().nextBytes(tag);
tags.add(new SessionTag(tag));
}
return tags;
}
/**
* Send an abuse message to the router
*/
public void reportAbuse(I2PSessionImpl session, int msgId, int severity) throws I2PSessionException {
ReportAbuseMessage msg = new ReportAbuseMessage();
MessageId id = new MessageId();
id.setMessageId(msgId);
msg.setMessageId(id);
AbuseReason reason = new AbuseReason();
reason.setReason("Not specified");
msg.setReason(reason);
AbuseSeverity sv = new AbuseSeverity();
sv.setSeverity(severity);
msg.setSeverity(sv);
session.sendMessage(msg);
}
/**
* Create a new signed leaseSet in response to a request to do so and send it
* to the router
*
*/
public void createLeaseSet(I2PSessionImpl session, LeaseSet leaseSet, SigningPrivateKey signingPriv, PrivateKey priv) throws I2PSessionException {
CreateLeaseSetMessage msg = new CreateLeaseSetMessage();
msg.setLeaseSet(leaseSet);
msg.setPrivateKey(priv);
msg.setSigningPrivateKey(signingPriv);
msg.setSessionId(session.getSessionId());
session.sendMessage(msg);
}
}

View File

@ -0,0 +1,67 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.I2PException;
import net.i2p.data.Destination;
import net.i2p.data.Certificate;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Properties;
/**
* Define the standard means of interacting with the I2P system
*
* @author jrandom
*/
public interface I2PClient {
/** Standard host property, defaulting to localhost if not specified */
public final static String PROP_TCP_HOST = "i2cp.tcp.host";
/** Standard port number property */
public final static String PROP_TCP_PORT = "i2cp.tcp.port";
/** Reliability property */
public final static String PROP_RELIABILITY = "i2cp.messageReliability";
/** Reliability value: best effort */
public final static String PROP_RELIABILITY_BEST_EFFORT = "BestEffort";
/** Reliability value: guaranteed */
public final static String PROP_RELIABILITY_GUARANTEED = "Guaranteed";
/** protocol flag that must be sent when opening the i2cp connection to the router */
public final static int PROTOCOL_BYTE = 0x2A;
/** Create a new client session for the Destination stored at the destKeyStream
* using the specified options to both connect to the router, to instruct
* the router how to handle the new session, and to configure the end to end
* encryption.
* @param destKeyStream location from which to read the Destination, PrivateKey, and SigningPrivateKey from
* @param options set of options to configure the router with
* @return new session allowing a Destination to recieve all of its messages and send messages to any other Destination.
*/
public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException;
/** Create a new destination with the default certificate creation properties and store
* it, along with the private encryption and signing keys at the specified location
* @param destKeyStream create a new destination and write out the object to the given stream,
* formatted as Destination, PrivateKey, and SigningPrivateKey
* @return new destination
*/
public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException;
/** Create a new destination with the given certificate and store it, along with the private
* encryption and signing keys at the specified location
*
* @param destKeyStream location to write out the destination, PrivateKey, and SigningPrivateKey
* @param cert certificate to tie to the destination
* @return newly created destination
*/
public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException;
}

View File

@ -0,0 +1,23 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
/**
* Provide a means of hooking into an appropriate I2PClient implementation
*
* @author jrandom
*/
public class I2PClientFactory {
/** Create a new instance of the appropriate I2PClient
* @return client implementation
*/
public static I2PClient createClient() {
return new I2PClientImpl();
}
}

View File

@ -0,0 +1,75 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import net.i2p.I2PException;
import net.i2p.crypto.KeyGenerator;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
/**
* Base client implementation
*
* @author jrandom
*/
class I2PClientImpl implements I2PClient {
/**
* Create the destination with a null payload
*/
public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException {
Certificate cert = new Certificate();
cert.setCertificateType(Certificate.CERTIFICATE_TYPE_NULL);
cert.setPayload(null);
return createDestination(destKeyStream, cert);
}
/**
* Create the destination with the given payload and write it out along with
* the PrivateKey and SigningPrivateKey to the destKeyStream
*
*/
public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException {
Destination d = new Destination();
d.setCertificate(cert);
PublicKey publicKey = new PublicKey();
Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair();
publicKey = (PublicKey)keypair[0];
PrivateKey privateKey = (PrivateKey)keypair[1];
Object signingKeys[] = KeyGenerator.getInstance().generateSigningKeypair();
SigningPublicKey signingPubKey = (SigningPublicKey)signingKeys[0];
SigningPrivateKey signingPrivKey = (SigningPrivateKey)signingKeys[1];
d.setPublicKey(publicKey);
d.setSigningPublicKey(signingPubKey);
d.writeBytes(destKeyStream);
privateKey.writeBytes(destKeyStream);
signingPrivKey.writeBytes(destKeyStream);
destKeyStream.flush();
return d;
}
/**
* Create a new session (though do not connect it yet)
*
*/
public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException {
//return new I2PSessionImpl(destKeyStream, options); // not thread safe
return new I2PSessionImpl2(destKeyStream, options); // thread safe
}
}

View File

@ -0,0 +1,41 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.*;
import net.i2p.util.Log;
import java.util.Map;
import java.util.HashMap;
/**
* Contains a map of message handlers that a session will want to use
*
* @author jrandom
*/
class I2PClientMessageHandlerMap {
private final static Log _log = new Log(I2PClientMessageHandlerMap.class);
/** map of message type id --> I2CPMessageHandler */
private static Map _handlers;
static {
_handlers = new HashMap();
_handlers.put(new Integer(DisconnectMessage.MESSAGE_TYPE), new DisconnectMessageHandler());
_handlers.put(new Integer(SessionStatusMessage.MESSAGE_TYPE), new SessionStatusMessageHandler());
_handlers.put(new Integer(RequestLeaseSetMessage.MESSAGE_TYPE), new RequestLeaseSetMessageHandler());
_handlers.put(new Integer(MessagePayloadMessage.MESSAGE_TYPE), new MessagePayloadMessageHandler());
_handlers.put(new Integer(MessageStatusMessage.MESSAGE_TYPE), new MessageStatusMessageHandler());
_handlers.put(new Integer(SetDateMessage.MESSAGE_TYPE), new SetDateMessageHandler());
}
public static I2CPMessageHandler getHandler(int messageTypeId) {
I2CPMessageHandler handler = (I2CPMessageHandler)_handlers.get(new Integer(messageTypeId));
return handler;
}
}

View File

@ -0,0 +1,109 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.Set;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
/**
* Define the standard means of sending and receiving messages on the
* I2P Network.
*
* @author jrandom
*/
public interface I2PSession {
/** Send a new message to the given destination, containing the specified
* payload, returning true if the router feels confident that the message
* was delivered.
* @param dest location to send the message
* @param payload body of the message to be sent (unencrypted)
* @return whether it was accepted by the router for delivery or not
*/
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
/**
* Like sendMessage above, except the key used and the tags sent are exposed to the
* application, so that if some application layer message delivery confirmation is used,
* rather than i2p's (slow) built in confirmation via guaranteed delivery mode, the
* application can update the SessionKeyManager, ala:
* <pre>
* SessionKeyManager.getInstance().tagsDelivered(dest.getPublicKey(), keyUsed, tagsSent);
* </pre>
* If an application is using guaranteed delivery mode, this is not useful, but for
* applications using best effort delivery mode, if they can know with certainty that a message
* was delivered and can update the SessionKeyManager appropriately, a significant performance
* boost will occur (subsequent message encryption and decryption will be done via AES and a SessionTag,
* rather than ElGamal+AES, which is 1000x slower).
*
* @param dest location to send the message
* @param payload body of the message to be sent (unencrypted)
* @param keyUsed session key delivered to the destination for association with the tags sent. This is essentially
* an output parameter - keyUsed.getData() is ignored during this call, but after the call completes,
* it will be filled with the bytes of the session key delivered. Typically the key delivered is the
* same one as the key encrypted with, but not always. If this is null then the key data will not be
* exposed.
* @param tagsSent set of tags delivered to the peer and associated with the keyUsed. This is also an output parameter -
* the contents of the set is ignored during the call, but afterwards it contains a set of SessionTag
* objects that were sent along side the given keyUsed.
*/
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
/** Receive a message that the router has notified the client about, returning
* the payload.
* @param msgId message to fetch
* @return unencrypted body of the message
*/
public byte[] receiveMessage(int msgId) throws I2PSessionException;
/** Instruct the router that the message received was abusive (including how
* abusive on a 1-100 scale) in the hopes the router can do something to
* minimize receiving abusive messages like that in the future.
* @param msgId message that was abusive (or -1 for not message related)
* @param severity how abusive
*/
public void reportAbuse(int msgId, int severity) throws I2PSessionException;
/** Instruct the I2PSession where it should send event notifications
* @param lsnr listener to retrieve events
*/
public void setSessionListener(I2PSessionListener lsnr);
/**
* Tear down the session and release any resources.
*
*/
public void destroySession() throws I2PSessionException;
/**
* Actually connect the session and start recieving/sending messages
*
*/
public void connect() throws I2PSessionException;
/**
* Retrieve the Destination this session serves as the endpoint for.
* Returns null if no destination is available.
*
*/
public Destination getMyDestination();
/**
* Retrieve the decryption PrivateKey associated with the Destination
*
*/
public PrivateKey getDecryptionKey();
/**
* Retrieve the signing SigningPrivateKey associated with the Destination
*/
public SigningPrivateKey getPrivateKey();
}

View File

@ -0,0 +1,28 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
import net.i2p.I2PException;
/**
* Thrown when there is a problem doing something on the session
*
* @author jrandom
*/
public class I2PSessionException extends I2PException {
private final static Log _log = new Log(I2PSessionException.class);
public I2PSessionException(String msg, Throwable t) {
super(msg, t);
}
public I2PSessionException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,492 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2cp.GetDateMessage;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Implementation of an I2P session running over TCP. This class is NOT thread safe -
* only one thread should send messages at any given time
*
* @author jrandom
*/
abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
private final static Log _log = new Log(I2PSessionImpl.class);
/** who we are */
private Destination _myDestination;
/** private key for decryption */
private PrivateKey _privateKey;
/** private key for signing */
private SigningPrivateKey _signingPrivateKey;
/** configuration options */
private Properties _options;
/** this session's Id */
private SessionId _sessionId;
/** currently granted lease set, or null */
private LeaseSet _leaseSet;
/** hostname of router */
private String _hostname;
/** port num to router */
private int _portNum;
/** socket for comm */
private Socket _socket;
/** reader that always searches for messages */
private I2CPMessageReader _reader;
/** where we pipe our messages */
private OutputStream _out;
/** who we send events to */
private I2PSessionListener _sessionListener;
/** class that generates new messages */
protected I2CPMessageProducer _producer;
/** map of integer --> MessagePayloadMessage */
Map _availableMessages;
/** MessageStatusMessage status from the most recent send that hasn't been consumed */
private List _receivedStatus;
private int _totalReconnectAttempts;
/** monitor for waiting until a lease set has been granted */
private Object _leaseSetWait = new Object();
/** whether the session connection has already been closed (or not yet opened) */
private boolean _closed;
/** have we received the current date from the router yet? */
private boolean _dateReceived;
/** lock that we wait upon, that the SetDateMessageHandler notifies */
private Object _dateReceivedLock = new Object();
void dateUpdated() {
_dateReceived = true;
synchronized (_dateReceivedLock) {
_dateReceivedLock.notifyAll();
}
}
/**
* Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
* from the destKeyStream, and using the specified options to connect to the router
*
* @throws I2PSessionException if there is a problem loading the private keys or
*/
public I2PSessionImpl(InputStream destKeyStream, Properties options) throws I2PSessionException {
_closed = true;
_producer = new I2CPMessageProducer();
_availableMessages = new HashMap();
try {
readDestination(destKeyStream);
} catch (DataFormatException dfe) {
throw new I2PSessionException("Error reading the destination key stream", dfe);
} catch (IOException ioe) {
throw new I2PSessionException("Error reading the destination key stream", ioe);
}
loadConfig(options);
_sessionId = null;
_receivedStatus = new LinkedList();
_leaseSet = null;
_totalReconnectAttempts = 0;
}
/**
* Parse the config for anything we know about
*
*/
private void loadConfig(Properties options) {
_options = new Properties();
_options.putAll(filter(options));
_hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, TestServer.LISTEN_PORT+"");
try {
_portNum = Integer.parseInt(portNum);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN)) _log.warn("Invalid port number specified, defaulting to "+TestServer.LISTEN_PORT, nfe);
_portNum = TestServer.LISTEN_PORT;
}
}
private static Properties filter(Properties options) {
Properties rv = new Properties();
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = options.getProperty(key);
if (key.startsWith("java")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping java.* property: " + key);
} else if (key.startsWith("user")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping user.* property: " + key);
} else if (key.startsWith("os")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping os.* property: " + key);
} else if (key.startsWith("sun")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping sun.* property: " + key);
} else if (key.startsWith("file")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping file.* property: " + key);
} else if (key.startsWith("line")) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping line.* property: " + key);
} else if ( (key.length() > 255) || (val.length() > 255) ) {
if (_log.shouldLog(Log.WARN)) _log.warn("Not passing on property [" + key + "] in the session configuration as the value is too long (max = 255): " + val);
} else {
rv.setProperty(key, val);
}
}
return rv;
}
void setLeaseSet(LeaseSet ls) {
_leaseSet = ls;
if (ls != null) {
synchronized (_leaseSetWait) {
_leaseSetWait.notifyAll();
}
}
}
LeaseSet getLeaseSet() { return _leaseSet; }
/**
* Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey
*
* @throws DataFormatException if the file is in the wrong format or keys are invalid
* @throws IOException if there is a problem reading the file
*/
private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException {
_myDestination = new Destination();
_privateKey = new PrivateKey();
_signingPrivateKey = new SigningPrivateKey();
_myDestination.readBytes(destKeyStream);
_privateKey.readBytes(destKeyStream);
_signingPrivateKey.readBytes(destKeyStream);
}
/**
* Connect to the router and establish a session. This call blocks until
* a session is granted.
*
* @throws I2PSessionException if there is a configuration error or the router is
* not reachable
*/
public void connect() throws I2PSessionException {
_closed = false;
long startConnect = Clock.getInstance().now();
try {
if (_log.shouldLog(Log.DEBUG)) _log.debug("connect begin to " + _hostname + ":" + _portNum);
_socket = new Socket(_hostname, _portNum);
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);
}
InputStream in = _socket.getInputStream();
_reader = new I2CPMessageReader(in, this);
if (_log.shouldLog(Log.DEBUG)) _log.debug("before startReading");
_reader.startReading();
if (_log.shouldLog(Log.DEBUG)) _log.debug("Before getDate");
sendMessage(new GetDateMessage());
if (_log.shouldLog(Log.DEBUG)) _log.debug("After getDate / begin waiting for a response");
while (!_dateReceived) {
try {
synchronized (_dateReceivedLock) { _dateReceivedLock.wait(1000); }
} catch (InterruptedException ie) {}
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("After received a SetDate response");
if (_log.shouldLog(Log.DEBUG)) _log.debug("Before producer.connect()");
_producer.connect(this);
if (_log.shouldLog(Log.DEBUG)) _log.debug("After producer.connect()");
// wait until we have created a lease set
while (_leaseSet == null) {
synchronized (_leaseSetWait) {
try { _leaseSetWait.wait(1000); } catch (InterruptedException ie) {}
}
}
long connected = Clock.getInstance().now();
if (_log.shouldLog(Log.INFO)) _log.info("Lease set created with inbound tunnels after " + (connected-startConnect) + "ms - ready to participate in the network!");
} catch (UnknownHostException uhe) {
_closed = true;
throw new I2PSessionException("Invalid session configuration", uhe);
} catch (IOException ioe) {
_closed = true;
throw new I2PSessionException("Problem connecting to " + _hostname + " on port " + _portNum, ioe);
}
}
/**
* Pull the unencrypted data from the message that we've already prefetched and
* notified the user that its available.
*
*/
public byte[] receiveMessage(int msgId) throws I2PSessionException {
MessagePayloadMessage msg = (MessagePayloadMessage)_availableMessages.remove(new Integer(msgId));
if (msg == null) return null;
return msg.getPayload().getUnencryptedData();
}
/**
* Report abuse with regards to the given messageId
*/
public void reportAbuse(int msgId, int severity) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed");
_producer.reportAbuse(this, msgId, severity);
}
/**
* Send the data to the destination.
* TODO: this currently always returns true, regardless of whether the message was
* delivered successfully. make this wait for at least ACCEPTED
*
*/
public abstract boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
public abstract boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public abstract void receiveStatus(int msgId, long nonce, int status);
protected boolean isGuaranteed() {
return I2PClient.PROP_RELIABILITY_GUARANTEED.equals(_options.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED));
}
protected static final Set createNewTags(int num) {
Set tags = new HashSet();
for (int i = 0; i < num; i++)
tags.add(new SessionTag(true));
return tags;
}
/**
* Recieve a payload message and let the app know its available
*/
public void addNewMessage(MessagePayloadMessage msg) {
_availableMessages.put(new Integer(msg.getMessageId().getMessageId()), msg);
final int id = msg.getMessageId().getMessageId();
byte data[] = msg.getPayload().getUnencryptedData();
if ( (data == null) || (data.length <= 0) ) {
if (_log.shouldLog(Log.ERROR)) _log.error("addNewMessage of a message with no unencrypted data", new Exception("Empty message"));
} else {
final long size = data.length;
Thread notifier = new I2PThread(new Runnable() {
public void run() {
if (_sessionListener != null)
_sessionListener.messageAvailable(I2PSessionImpl.this, id, size);
}
});
notifier.setName("Notifier [" + _sessionId + "/" + id + "]");
notifier.setDaemon(true);
notifier.start();
}
}
/**
* Recieve notification of some I2CP message and handle it if possible
*
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
I2CPMessageHandler handler = I2PClientMessageHandlerMap.getHandler(message.getType());
if (handler == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Unknown message or unhandleable message received: type = " + message.getType());
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Message received of type " + message.getType() + " to be handled by " + handler);
handler.handleMessage(message, this);
}
}
/**
* Recieve notifiation of an error reading the I2CP stream
*
*/
public void readError(I2CPMessageReader reader, Exception error) {
propogateError("There was an error reading data", error);
disconnect();
}
/**
* Retrieve the destination of the session
*/
public Destination getMyDestination() { return _myDestination; }
/**
* Retrieve the decryption PrivateKey
*/
public PrivateKey getDecryptionKey() { return _privateKey; }
/**
* Retrieve the signing SigningPrivateKey
*/
public SigningPrivateKey getPrivateKey() { return _signingPrivateKey; }
/**
* Retrieve the helper that generates I2CP messages
*/
I2CPMessageProducer getProducer() { return _producer; }
/**
* Retrieve the configuration options
*/
Properties getOptions() { return _options; }
/**
* Retrieve the session's ID
*/
SessionId getSessionId() { return _sessionId; }
/**
* Configure the session's ID
*/
void setSessionId(SessionId id) {
_sessionId = id;
}
/** configure the listener */
public void setSessionListener(I2PSessionListener lsnr) { _sessionListener = lsnr; }
/** has the session been closed (or not yet connected)? */
public boolean isClosed() { return _closed; }
/**
* Deliver an I2CP message to the router
*
* @throws I2PSessionException if the message is malformed or there is an error writing it out
*/
void sendMessage(I2CPMessage message) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed");
try {
synchronized(_out) {
message.writeMessage(_out);
_out.flush();
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("Message written out and flushed");
} catch (I2CPMessageException ime) {
throw new I2PSessionException("Error writing out the message", ime);
} catch (IOException ioe) {
throw new I2PSessionException("Error writing out the message", ioe);
}
}
/**
* Pass off the error to the listener
*/
void propogateError(String msg, Throwable error) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred: " + msg, error);
if (_sessionListener != null)
_sessionListener.errorOccurred(this, msg, error);
}
/**
* Tear down the session, and do NOT reconnect
*/
public void destroySession() { destroySession(true); }
public void destroySession(boolean sendDisconnect) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Destroy the session", new Exception("DestroySession()"));
_closed = true;
if (sendDisconnect) {
try {
_producer.disconnect(this);
} catch (I2PSessionException ipe) {
propogateError("Error destroying the session", ipe);
}
}
closeSocket();
if (_sessionListener != null)
_sessionListener.disconnected(this);
}
/**
* Close the socket carefully
*
*/
private void closeSocket() {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Closing the socket", new Exception("closeSocket"));
_closed = true;
if (_reader != null)
_reader.stopReading();
_reader = null;
if (_socket != null) {
try {
_socket.close();
} catch (IOException ioe) {
propogateError("Caught an IO error closing the socket. ignored", ioe);
} finally {
_socket = null; // so when propogateError calls closeSocket, it doesnt loop
}
}
}
/**
* Recieve notification that the I2CP connection was disconnected
*/
public void disconnected(I2CPMessageReader reader) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Disconnected", new Exception("Disconnected"));
disconnect();
}
protected void disconnect() {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Disconnect() called", new Exception("Disconnect"));
if (shouldReconnect()) {
if (reconnect()) {
if (_log.shouldLog(Log.INFO)) _log.info("I2CP reconnection successful");
return;
} else {
_log.error("I2CP reconnection failed");
}
}
_log.error("Disconned from the router, and not trying to reconnect further. I hope you're not hoping anything else will happen");
if (_sessionListener != null)
_sessionListener.disconnected(this);
closeSocket();
}
private final static int MAX_RECONNECT_ATTEMPTS = 1;
private final static int MAX_TOTAL_RECONNECT_ATTEMPTS = 3;
protected boolean shouldReconnect() { return true; }
protected boolean reconnect() {
closeSocket();
if (_totalReconnectAttempts < MAX_TOTAL_RECONNECT_ATTEMPTS) {
_totalReconnectAttempts++;
} else {
if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "Max number of reconnects exceeded [" + _totalReconnectAttempts + "], we give up!");
return false;
}
if (_log.shouldLog(Log.INFO)) _log.info("Reconnecting...");
for (int i = 0; i < MAX_RECONNECT_ATTEMPTS; i++) {
try {
connect();
return true;
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error reconnecting on attempt " + i, ise);
}
}
return false;
}
}

View File

@ -0,0 +1,287 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.DataHelper;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* Thread safe implementation of an I2P session running over TCP.
*
* @author jrandom
*/
class I2PSessionImpl2 extends I2PSessionImpl {
private final static Log _log = new Log(I2PSessionImpl2.class);
/** set of MessageState objects, representing all of the messages in the process of being sent */
private Set _sendingStates;
/** max # seconds to wait for confirmation of the message send */
private final static long SEND_TIMEOUT = 60*1000; // 60 seconds to send
/** should we gzip each payload prior to sending it? */
private final static boolean SHOULD_COMPRESS = true;
/**
* Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
* from the destKeyStream, and using the specified options to connect to the router
*
* @throws I2PSessionException if there is a problem loading the private keys or
*/
public I2PSessionImpl2(InputStream destKeyStream, Properties options) throws I2PSessionException {
super(destKeyStream, options);
_sendingStates = new HashSet(32);
}
protected long getTimeout() { return SEND_TIMEOUT; }
public void destroySession(boolean sendDisconnect) {
clearStates();
super.destroySession(sendDisconnect);
}
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
}
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed");
if (SHOULD_COMPRESS)
payload = DataHelper.compress(payload);
if (isGuaranteed()) {
return sendGuaranteed(dest, payload, keyUsed, tagsSent);
} else {
return sendBestEffort(dest, payload, keyUsed, tagsSent);
}
}
/**
* pull the unencrypted AND DECOMPRESSED data
*/
public byte[] receiveMessage(int msgId) throws I2PSessionException {
byte compressed[] = super.receiveMessage(msgId);
if (SHOULD_COMPRESS)
return DataHelper.decompress(compressed);
else
return compressed;
}
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
if (key == null)
key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
Set sentTags = null;
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
sentTags = createNewTags(50);
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30*1000) {
// if we have > 10 tags, but they expire in under 30 seconds, we want more
sentTags = createNewTags(50);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Tags are almost expired, adding 50 new ones");
}
SessionKey newKey = null;
if (false) // rekey
newKey = KeyGenerator.getInstance().generateSessionKey();
long nonce = (long)RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
MessageState state = new MessageState(nonce);
state.setKey(key);
state.setTags(sentTags);
state.setNewKey(newKey);
state.setTo(dest);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Setting key = " + key);
if (keyUsed != null) {
if (newKey != null)
keyUsed.setData(newKey.getData());
else
keyUsed.setData(key.getData());
}
if (tagsSent != null) {
if (sentTags != null) {
tagsSent.addAll(sentTags);
}
}
synchronized (_sendingStates) {
_sendingStates.add(state);
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding sending state " + state.getMessageId() + " / " + state.getNonce());
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, Clock.getInstance().now() + getTimeout());
synchronized (_sendingStates) {
_sendingStates.remove(state);
}
boolean found = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if (_log.shouldLog(Log.DEBUG)) _log.debug("After waitFor sending state " + state.getMessageId().getMessageId() + " / " + state.getNonce() + " found = " + found);
if (found) {
if (_log.shouldLog(Log.INFO)) _log.info("Message sent after " + state.getElapsed() + "ms with " + payload.length + " bytes");
} else {
if (_log.shouldLog(Log.INFO)) _log.info("Message send failed after " + state.getElapsed() + "ms with " + payload.length + " bytes");
if (_log.shouldLog(Log.ERROR)) _log.error("Never received *accepted* from the router! dropping and reconnecting");
disconnect();
return false;
}
return found;
}
private boolean sendGuaranteed(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
if (key == null)
key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
Set sentTags = null;
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
sentTags = createNewTags(50);
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30*1000) {
// if we have > 10 tags, but they expire in under 30 seconds, we want more
sentTags = createNewTags(50);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Tags are almost expired, adding 50 new ones");
}
SessionKey newKey = null;
if (false) // rekey
newKey = KeyGenerator.getInstance().generateSessionKey();
long nonce = (long)RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
MessageState state = new MessageState(nonce);
state.setKey(key);
state.setTags(sentTags);
state.setNewKey(newKey);
state.setTo(dest);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Setting key = " + key);
if (keyUsed != null) {
if (newKey != null)
keyUsed.setData(newKey.getData());
else
keyUsed.setData(key.getData());
}
if (tagsSent != null) {
if (sentTags != null) {
tagsSent.addAll(sentTags);
}
}
synchronized (_sendingStates) {
_sendingStates.add(state);
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding sending state " + state.getMessageId() + " / " + state.getNonce());
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
state.waitFor(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS, Clock.getInstance().now() + SEND_TIMEOUT);
synchronized (_sendingStates) {
_sendingStates.remove(state);
}
boolean found = state.received(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
boolean accepted = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if ( (!accepted) || (state.getMessageId() == null) ) {
if (_log.shouldLog(Log.ERROR)) _log.error("State with nonce " + state.getNonce() + " was not accepted? (no messageId!!)");
nackTags(state);
if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT,"Disconnecting/reconnecting because we never were accepted!");
disconnect();
return false;
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("After waitFor sending state " + state.getMessageId().getMessageId() + " / " + state.getNonce() + " found = " + found);
if (found) {
if (_log.shouldLog(Log.INFO)) _log.info("Message sent after " + state.getElapsed() + "ms with " + payload.length + " bytes");
ackTags(state);
} else {
if (_log.shouldLog(Log.INFO)) _log.info("Message send failed after " + state.getElapsed() + "ms with " + payload.length + " bytes");
nackTags(state);
}
return found;
}
private void ackTags(MessageState state) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("ack tags for msgId " + state.getMessageId() + " / " + state.getNonce() + " key = " + state.getKey() + ", tags = " + state.getTags());
if ( (state.getTags() != null) && (state.getTags().size() > 0) ) {
if (state.getNewKey() == null)
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getKey(), state.getTags());
else
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getNewKey(), state.getTags());
}
}
private void nackTags(MessageState state) {
if (_log.shouldLog(Log.INFO)) _log.info("nack tags for msgId " + state.getMessageId() + " / " + state.getNonce() + " key = " + state.getKey());
SessionKeyManager.getInstance().failTags(state.getTo().getPublicKey());
}
public void receiveStatus(int msgId, long nonce, int status) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received status " + status + " for msgId " + msgId + " / " + nonce);
MessageState state = null;
synchronized (_sendingStates) {
for (Iterator iter = _sendingStates.iterator(); iter.hasNext(); ) {
state = (MessageState)iter.next();
if (_log.shouldLog(Log.DEBUG)) _log.debug("State " + state.getMessageId() + " / " + state.getNonce());
if (state.getNonce() == nonce) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Found a matching state");
break;
} else if ( (state.getMessageId() != null) && (state.getMessageId().getMessageId() == msgId) ) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Found a matching state by msgId");
break;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("State does not match");
state = null;
}
}
}
if (state != null) {
if (state.getMessageId() == null) {
MessageId id = new MessageId();
id.setMessageId(msgId);
state.setMessageId(id);
}
state.receive(status);
} else {
if (_log.shouldLog(Log.INFO)) _log.info("No matching state for messageId " + msgId + " / " + nonce + " w/ status = " + status);
}
}
/**
* Called whenever we want to reconnect (used only in the superclass). We need
* to override this to clear out the message state
*
*/
protected boolean reconnect() {
// even if we succeed in reconnecting, we want to clear the old states,
// since this will be a new sessionId
clearStates();
return super.reconnect();
}
private void clearStates() {
synchronized (_sendingStates) {
for (Iterator iter = _sendingStates.iterator(); iter.hasNext(); ) {
MessageState state = (MessageState)iter.next();
state.cancel();
}
if (_log.shouldLog(Log.INFO)) _log.info("Disconnecting " + _sendingStates.size() + " states");
_sendingStates.clear();
}
}
}

View File

@ -0,0 +1,44 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
/**
* Define a means for the router to asynchronously notify the client that a
* new message is available or the router is under attack.
*
* @author jrandom
*/
public interface I2PSessionListener {
/** Instruct the client that the given session has received a message with
* size # of bytes.
* @param session session to notify
* @param msgId message number available
* @param size size of the message
*/
void messageAvailable(I2PSession session, int msgId, long size);
/** Instruct the client that the session specified seems to be under attack
* and that the client may wish to move its destination to another router.
* @param session session to report abuse to
* @param severity how bad the abuse is
*/
void reportAbuse(I2PSession session, int severity);
/**
* Notify the client that the session has been terminated
*
*/
void disconnected(I2PSession session);
/**
* Notify the client that some error occurred
*
*/
void errorOccurred(I2PSession session, String message, Throwable error);
}

View File

@ -0,0 +1,62 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.data.DataFormatException;
import net.i2p.data.Payload;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
/**
* Handle I2CP MessagePayloadMessages from the router delivering the contents
* of a message by accepting it, decrypting the payload, adding it to the set of
* recieved messages, and telling the router that it has been recieved correctly.
*
* @author jrandom
*/
class MessagePayloadMessageHandler extends HandlerImpl {
public MessagePayloadMessageHandler() {
super(MessagePayloadMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
try {
MessagePayloadMessage msg = (MessagePayloadMessage)message;
MessageId id = msg.getMessageId();
Payload payload = decryptPayload(msg, session);
session.addNewMessage(msg);
ReceiveMessageEndMessage m = new ReceiveMessageEndMessage();
m.setMessageId(id);
m.setSessionId(msg.getSessionId());
session.sendMessage(m);
} catch (DataFormatException dfe) {
session.propogateError("Error handling a new payload message", dfe);
} catch (I2PSessionException ise) {
session.propogateError("Error handling a new payload message", ise);
}
}
/**
* Decrypt the payload
*/
private Payload decryptPayload(MessagePayloadMessage msg, I2PSessionImpl session) throws DataFormatException {
Payload payload = msg.getPayload();
byte[] data = ElGamalAESEngine.decrypt(payload.getEncryptedData(), session.getDecryptionKey());
if (data == null) {
_log.error("Error decrypting the payload to public key " + session.getMyDestination().getPublicKey().toBase64() + "\nPayload: " + payload.calculateHash());
throw new DataFormatException("Unable to decrypt the payload");
}
payload.setUnencryptedData(data);
return payload;
}
}

View File

@ -0,0 +1,208 @@
package net.i2p.client;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.SessionKey;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Contains the state of a payload message being sent to a peer
*
*/
class MessageState {
private final static Log _log = new Log(MessageState.class);
private long _nonce;
private MessageId _id;
private Set _receivedStatus;
private SessionKey _key;
private SessionKey _newKey;
private Set _tags;
private Destination _to;
private boolean _cancelled;
private long _created;
private Object _lock = new Object();
public MessageState(long nonce) {
_nonce = nonce;
_id = null;
_receivedStatus = new HashSet();
_cancelled = false;
_key = null;
_newKey = null;
_tags = null;
_to = null;
_created = Clock.getInstance().now();
}
public void receive(int status) {
synchronized (_receivedStatus) {
_receivedStatus.add(new Integer(status));
}
synchronized (_lock) {
_lock.notifyAll();
}
}
public void setMessageId(MessageId id) { _id = id; }
public MessageId getMessageId() { return _id; }
public long getNonce() { return _nonce; }
public void setKey(SessionKey key) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Setting key [" + _key + "] to [" + key + "]");
_key = key;
}
public SessionKey getKey() { return _key; }
public void setNewKey(SessionKey key) { _newKey = key; }
public SessionKey getNewKey() { return _newKey; }
public void setTags(Set tags) { _tags = tags; }
public Set getTags() { return _tags; }
public void setTo(Destination dest) { _to = dest; }
public Destination getTo() { return _to; }
public long getElapsed() { return Clock.getInstance().now() - _created; }
public void waitFor(int status, long expiration) {
while (true) {
if (_cancelled) return;
long timeToWait = expiration - Clock.getInstance().now();
if (timeToWait <= 0) {
if (_log.shouldLog(Log.WARN)) _log.warn("Expired waiting for the status [" + status + "]");
return;
}
if (isSuccess(status) || isFailure(status)) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received a confirm (one way or the other)");
return;
}
if (timeToWait > 5000) {
timeToWait = 5000;
}
synchronized (_lock) {
try {
_lock.wait(timeToWait);
} catch (InterruptedException ie) {}
}
}
}
private boolean isSuccess(int wantedStatus) {
List received = null;
synchronized (_receivedStatus) {
received = new ArrayList(_receivedStatus);
//_receivedStatus.clear();
}
boolean rv = false;
if (_log.shouldLog(Log.DEBUG)) _log.debug("isSuccess(" + wantedStatus + "): " + received);
for (Iterator iter = received.iterator(); iter.hasNext(); ) {
Integer val = (Integer)iter.next();
int recv = val.intValue();
switch (recv) {
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
if (_log.shouldLog(Log.WARN)) _log.warn("Received best effort failure after " + getElapsed() + " from " + this.toString());
rv = false;
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
if (_log.shouldLog(Log.WARN)) _log.warn("Received guaranteed failure after " + getElapsed() + " from " + this.toString());
rv = false;
break;
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
return true; // if we're only looking for accepted, take it directly (don't let any GUARANTEED_* override it)
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Got accepted, but we're waiting for more from " + this.toString());
continue;
// ignore accepted, as we want something better
}
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received best effort success after " + getElapsed() + " from " + this.toString());
if (wantedStatus == recv) {
rv = true;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Not guaranteed success, but best effort after " + getElapsed() + " will do... from " + this.toString());
rv = true;
}
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received guaranteed success after " + getElapsed() + " from " + this.toString());
// even if we're waiting for best effort success, guaranteed is good enough
rv = true;
break;
case -1:
continue;
default:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received something else [" + recv + "]...");
}
}
return rv;
}
private boolean isFailure(int wantedStatus) {
List received = null;
synchronized (_receivedStatus) {
received = new ArrayList(_receivedStatus);
//_receivedStatus.clear();
}
boolean rv = false;
if (_log.shouldLog(Log.DEBUG)) _log.debug("isFailure(" + wantedStatus + "): " + received);
for (Iterator iter = received.iterator(); iter.hasNext(); ) {
Integer val = (Integer)iter.next();
int recv = val.intValue();
switch (recv) {
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
if (_log.shouldLog(Log.DEBUG)) _log.warn("Received best effort failure after " + getElapsed() + " from " + this.toString());
rv = true;
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
if (_log.shouldLog(Log.DEBUG)) _log.warn("Received guaranteed failure after " + getElapsed() + " from " + this.toString());
rv = true;
break;
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
rv = false;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Got accepted, but we're waiting for more from " + this.toString());
continue;
// ignore accepted, as we want something better
}
break;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received best effort success after " + getElapsed() + " from " + this.toString());
if (wantedStatus == recv) {
rv = false;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Not guaranteed success, but best effort after " + getElapsed() + " will do... from " + this.toString());
rv = false;
}
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received guaranteed success after " + getElapsed() + " from " + this.toString());
// even if we're waiting for best effort success, guaranteed is good enough
rv = false;
break;
case -1:
continue;
default:
if (_log.shouldLog(Log.DEBUG)) _log.debug("Received something else [" + recv + "]...");
}
}
return rv;
}
/** true if the given status (or an equivilant) was received */
public boolean received(int status) {
return isSuccess(status);
}
public void cancel() {
_cancelled = true;
synchronized (_lock) {
_lock.notifyAll();
}
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.*;
/**
* Handle I2CP MessageStatusMessages from the router. This currently only takes
* into account status of available, automatically prefetching them as soon as
* possible
*
* @author jrandom
*/
class MessageStatusMessageHandler extends HandlerImpl {
public MessageStatusMessageHandler() {
super(MessageStatusMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
boolean skipStatus = true;
if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions().getProperty(
I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT)))
skipStatus = false;
_log.debug("Handle message " + message);
MessageStatusMessage msg = (MessageStatusMessage)message;
switch (msg.getStatus()) {
case MessageStatusMessage.STATUS_AVAILABLE:
ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage();
m.setMessageId(msg.getMessageId());
m.setSessionId(msg.getSessionId());
try {
session.sendMessage(m);
} catch (I2PSessionException ise) {
_log.error("Error asking for the message", ise);
}
return;
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
// noop
return;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
_log.info("Message delivery succeeded for message " + msg.getMessageId());
//if (!skipStatus)
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
return;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
_log.info("Message delivery FAILED for message " + msg.getMessageId());
//if (!skipStatus)
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
return;
default:
_log.error("Invalid message delivery status received: " + msg.getStatus());
}
}
}

View File

@ -0,0 +1,123 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.HashMap;
import java.util.Map;
import net.i2p.crypto.KeyGenerator;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.util.Log;
/**
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases
*
* @author jrandom
*/
class RequestLeaseSetMessageHandler extends HandlerImpl {
private final static Log _log = new Log(RequestLeaseSetMessageHandler.class);
private Map _existingLeaseSets;
public RequestLeaseSetMessageHandler() {
super(RequestLeaseSetMessage.MESSAGE_TYPE);
_existingLeaseSets = new HashMap(32);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
RequestLeaseSetMessage msg = (RequestLeaseSetMessage)message;
LeaseSet leaseSet = new LeaseSet();
for (int i = 0; i < msg.getEndpoints(); i++) {
Lease lease = new Lease();
lease.setRouterIdentity(msg.getRouter(i));
lease.setTunnelId(msg.getTunnelId(i));
lease.setEndDate(msg.getEndDate());
//lease.setStartDate(msg.getStartDate());
leaseSet.addLease(lease);
}
// also, if this session is connected to multiple routers, include other leases here
leaseSet.setDestination(session.getMyDestination());
// reuse the old keys for the client
LeaseInfo li = null;
synchronized (_existingLeaseSets) {
if (_existingLeaseSets.containsKey(session.getMyDestination()))
li = (LeaseInfo)_existingLeaseSets.get(session.getMyDestination());
}
if (li == null) {
li = new LeaseInfo(session.getMyDestination());
synchronized (_existingLeaseSets) {
_existingLeaseSets.put(session.getMyDestination(), li);
}
_log.debug("Creating new leaseInfo keys", new Exception("new leaseInfo keys"));
} else {
_log.debug("Caching the old leaseInfo keys", new Exception("cached! w00t"));
}
leaseSet.setEncryptionKey(li.getPublicKey());
leaseSet.setSigningKey(li.getSigningPublicKey());
try {
leaseSet.sign(session.getPrivateKey());
session.getProducer().createLeaseSet(session, leaseSet, li.getSigningPrivateKey(), li.getPrivateKey());
session.setLeaseSet(leaseSet);
} catch (DataFormatException dfe) {
session.propogateError("Error signing the leaseSet", dfe);
} catch (I2PSessionException ise) {
session.propogateError("Error sending the signed leaseSet", ise);
}
}
private static class LeaseInfo {
private PublicKey _pubKey;
private PrivateKey _privKey;
private SigningPublicKey _signingPubKey;
private SigningPrivateKey _signingPrivKey;
private Destination _dest;
public LeaseInfo(Destination dest) {
_dest = dest;
Object encKeys[] = KeyGenerator.getInstance().generatePKIKeypair();
Object signKeys[] = KeyGenerator.getInstance().generateSigningKeypair();
_pubKey = (PublicKey)encKeys[0];
_privKey = (PrivateKey)encKeys[1];
_signingPubKey = (SigningPublicKey)signKeys[0];
_signingPrivKey = (SigningPrivateKey)signKeys[1];
}
public PublicKey getPublicKey() { return _pubKey; }
public PrivateKey getPrivateKey() { return _privKey; }
public SigningPublicKey getSigningPublicKey() { return _signingPubKey; }
public SigningPrivateKey getSigningPrivateKey() { return _signingPrivKey; }
public int hashCode() {
return DataHelper.hashCode(_pubKey) +
7*DataHelper.hashCode(_privKey) +
7*7*DataHelper.hashCode(_signingPubKey) +
7*7*7*DataHelper.hashCode(_signingPrivKey);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof LeaseInfo) )
return false;
LeaseInfo li = (LeaseInfo)obj;
return DataHelper.eq(_pubKey, li.getPublicKey()) &&
DataHelper.eq(_privKey, li.getPrivateKey()) &&
DataHelper.eq(_signingPubKey, li.getSigningPublicKey()) &&
DataHelper.eq(_signingPrivKey, li.getSigningPrivateKey());
}
}
}

View File

@ -0,0 +1,46 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.*;
/**
* Handle I2CP SessionStatusMessagese from the router, updating the session as
* necssary.
*
* @author jrandom
*/
class SessionStatusMessageHandler extends HandlerImpl {
public SessionStatusMessageHandler() {
super(SessionStatusMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
SessionStatusMessage msg = (SessionStatusMessage)message;
session.setSessionId(msg.getSessionId());
switch (msg.getStatus()) {
case SessionStatusMessage.STATUS_CREATED:
_log.info("Session created successfully");
break;
case SessionStatusMessage.STATUS_DESTROYED:
_log.info("Session destroyed");
session.destroySession();
break;
case SessionStatusMessage.STATUS_INVALID:
session.destroySession();
break;
case SessionStatusMessage.STATUS_UPDATED:
_log.info("Session status updated");
break;
default:
_log.warn("Unknown session status sent: " + msg.getStatus());
}
return;
}
}

View File

@ -0,0 +1,29 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.i2cp.*;
import net.i2p.util.Clock;
/**
* Handle I2CP time messages from the router
*
* @author jrandom
*/
class SetDateMessageHandler extends HandlerImpl {
public SetDateMessageHandler() {
super(SetDateMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
SetDateMessage msg = (SetDateMessage)message;
Clock.getInstance().setNow(msg.getDate().getTime());
session.dateUpdated();
}
}

View File

@ -0,0 +1,121 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Quick and dirty test harness for sending messages from one destination to another.
* This will print out some debugging information and containg the statement
* "Hello other side. I am dest1" if the router and the client libraries work.
* This class bootstraps itself each time - creating new keys and destinations
*
* @author jrandom
*/
public class TestClient implements I2PSessionListener {
private final static Log _log = new Log(TestClient.class);
private static Destination _dest1;
private static Destination _dest2;
private boolean _stillRunning;
public void runTest(String keyfile, boolean isDest1) {
_stillRunning = true;
try {
I2PClient client = I2PClientFactory.createClient();
File file = new File(keyfile);
Destination d = client.createDestination(new FileOutputStream(file));
if (isDest1)
_dest1 = d;
else
_dest2 = d;
_log.debug("Destination written to " + file.getAbsolutePath());
Properties options = new Properties();
if (System.getProperty(I2PClient.PROP_TCP_HOST) == null)
options.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
else
options.setProperty(I2PClient.PROP_TCP_HOST, System.getProperty(I2PClient.PROP_TCP_HOST));
if (System.getProperty(I2PClient.PROP_TCP_PORT) == null)
options.setProperty(I2PClient.PROP_TCP_PORT, "7654");
else
options.setProperty(I2PClient.PROP_TCP_PORT, System.getProperty(I2PClient.PROP_TCP_PORT));
I2PSession session = client.createSession(new FileInputStream(file), options);
session.setSessionListener(this);
_log.debug("Before connect...");
session.connect();
_log.debug("Connected");
// wait until the other one is connected
while ( (_dest1 == null) || (_dest2 == null) )
try { Thread.sleep(500); } catch (InterruptedException ie) {}
if (isDest1) {
Destination otherD = (isDest1 ? _dest2 : _dest1);
boolean accepted = session.sendMessage(otherD, ("Hello other side. I am" + (isDest1 ? "" : " NOT") + " dest1").getBytes());
} else {
while (_stillRunning) {
try {
_log.debug("waiting for a message...");
Thread.sleep(1000);
} catch (InterruptedException ie) {}
}
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
System.exit(0);
}
//session.destroySession();
} catch (Exception e) {
_log.error("Error running the test for isDest1? " + isDest1, e);
}
}
public static void main(String args[]) {
doTest();
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
static void doTest() {
Thread test1 = new I2PThread(new Runnable() { public void run() { (new TestClient()).runTest("test1.keyfile", true); } } );
Thread test2 = new I2PThread(new Runnable() { public void run() { (new TestClient()).runTest("test2.keyfile", false); } } );
test1.start();
test2.start();
_log.debug("Test threads started");
}
public void disconnected(I2PSession session) {
_log.debug("Disconnected");
_stillRunning = false;
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_log.debug("Error occurred: " + message, error);
}
public void messageAvailable(I2PSession session, int msgId, long size) {
_log.debug("Message available for us! id = " + msgId + " of size " + size);
try {
byte msg[] = session.receiveMessage(msgId);
_log.debug("Content of message " + msgId+ ":\n"+new String(msg));
_stillRunning = false;
} catch (I2PSessionException ise) {
_log.error("Error fetching available message", ise);
}
}
public void reportAbuse(I2PSession session, int severity) {
_log.debug("Abuse reported of severity " + severity);
}
}

View File

@ -0,0 +1,84 @@
package net.i2p.client;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Implement a local only router for testing purposes. This router is minimal
* in that it doesn't verify signatures, communicate with other routers, or handle
* failures very gracefully. It is simply a test harness to allow I2CP based
* applications to run.
*
* @author jrandom
*/
public class TestServer implements Runnable {
private final static Log _log = new Log(TestServer.class);
private ServerSocket _socket;
public static int LISTEN_PORT = 7654;
protected void setPort(int port) { LISTEN_PORT = port; }
/**
* Start up the socket listener, listens for connections, and
* fires those connections off via {@link #runConnection runConnection}.
* This only returns if the socket cannot be opened or there is a catastrophic
* failure.
*
*/
public void runServer() {
try {
_socket = new ServerSocket(LISTEN_PORT);
} catch (IOException ioe) {
_log.error("Error listening", ioe);
return;
}
while (true) {
try {
Socket socket = _socket.accept();
runConnection(socket);
} catch (IOException ioe) {
_log.error("Server error accepting", ioe);
}
}
}
/**
* Handle the connection by passing it off to a {@link ConnectionRunner ConnectionRunner}
*
*/
protected void runConnection(Socket socket) throws IOException {
ConnectionRunner runner = new ConnectionRunner(socket);
runner.doYourThing();
}
public void run() { runServer(); }
/**
* Fire up the router
*/
public static void main(String args[]) {
if (args.length == 1) {
} else if (args.length == 2) {
try {
LISTEN_PORT = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
_log.error("Invalid port number specified (" + args[1] + "), using " + LISTEN_PORT, nfe);
}
}
TestServer server = new TestServer();
Thread t = new I2PThread(server);
t.start();
}
}

View File

@ -0,0 +1,23 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import net.i2p.data.Destination;
/**
* A Dummy naming service that can only handle base64 destinations.
*/
class DummyNamingService extends NamingService {
public Destination lookup(String hostname) {
return lookupBase64(hostname);
}
public String reverseLookup(Destination dest) {
return null;
}
}

View File

@ -0,0 +1,66 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* A naming service based on the "hosts.txt" file.
*/
public class HostsTxtNamingService extends NamingService {
/**
* If this system property is specified, the tunnel will read the
* given file for hostname=destKey values when resolving names
*/
public final static String PROP_HOSTS_FILE = "i2p.hostsfile";
/** default hosts.txt filename */
public final static String DEFAULT_HOSTS_FILE = "hosts.txt";
private final static Log _log = new Log(HostsTxtNamingService.class);
public Destination lookup(String hostname) {
// Try to look it up in hosts.txt
// Reload file each time to catch changes.
// (and it's easier :P
String hostsfile=System.getProperty(PROP_HOSTS_FILE,
DEFAULT_HOSTS_FILE );
Properties hosts=new Properties();
FileInputStream fis = null;
try {
File f = new File(hostsfile);
if(f.canRead()) {
fis = new FileInputStream(f);
hosts.load(fis);
} else {
_log.error("Hosts file " + hostsfile + " does not exist.");
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe );
} finally {
if (fis != null) try {fis.close();} catch (IOException ioe) {}
}
String res = hosts.getProperty(hostname);
// If we can't find name in hosts, assume it's a key.
if ((res == null) || (res.trim().length() == 0)) {
res = hostname;
}
return lookupBase64(res);
}
public String reverseLookup(Destination dest) {
return null;
}
}

View File

@ -0,0 +1,78 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Naming services create a subclass of this class.
*/
public abstract class NamingService {
private final static Log _log = new Log(NamingService.class);
private static final String PROP_IMPL = "i2p.naming.impl";
private static final String DEFAULT_IMPL=
"net.i2p.client.naming.HostsTxtNamingService";
/**
* Look up a host name.
* @return the Destination for this host name, or
* <code>null</code> if name is unknown.
*/
public abstract Destination lookup(String hostname);
/**
* Reverse look up a destination
* @return a host name for this Destination, or <code>null</code>
* if none is known. It is safe for subclasses to always return
* <code>null</code> if no reverse lookup is possible.
*/
public abstract String reverseLookup(Destination dest);
/**
* Check if host name is valid Base64 encoded dest and return this
* dest in that case. Useful as a "fallback" in custom naming
* implementations.
*/
protected Destination lookupBase64(String hostname) {
try {
Destination result = new Destination();
result.fromBase64(hostname);
return result;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error translating [" + hostname + "]", dfe);
return null;
}
}
private static NamingService instance = null;
/**
* Get a naming service instance. This method ensures that there
* will be only one naming service instance (singleton) as well as
* choose the implementation from the "i2p.naming.impl" system
* property.
*/
public static synchronized NamingService getInstance() {
if (instance == null) {
String impl = System.getProperty(PROP_IMPL,
DEFAULT_IMPL);
try {
instance = (NamingService) Class.forName(impl).newInstance();
} catch (Exception ex) {
_log.error("Cannot loadNaming service implementation", ex);
instance = new DummyNamingService(); // fallback
}
}
return instance;
}
}

View File

@ -0,0 +1,145 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.DataFormatException;
import net.i2p.data.SessionKey;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
/**
* Wrapper singleton for AES cypher operation.
*
* @author jrandom
* @license GPL
*/
public class AESEngine {
private final static Log _log = new Log(AESEngine.class);
private static AESEngine _engine;
static {
if ("off".equals(System.getProperty("i2p.encryption", "on")))
_engine = new AESEngine();
else
_engine = new CryptixAESEngine();
}
public static AESEngine getInstance() { return _engine; }
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param sessionKey private esession key to encrypt to
* @param initializationVector IV for CBC
* @return encrypted data
*/
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ( (initializationVector == null) || (payload == null) || (sessionKey == null) || (initializationVector.length != 16) )
return null;
byte cyphertext[] = new byte[payload.length+(16-(payload.length%16))];
_log.warn("Warning: AES is disabled");
System.arraycopy(payload, 0, cyphertext, 0, payload.length);
return cyphertext;
}
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
if ( (iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16) )
return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize+64);
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
try {
h.writeBytes(baos);
DataHelper.writeLong(baos, 4, payload.length);
baos.write(payload);
byte tv[] = baos.toByteArray();
baos.write(ElGamalAESEngine.getPadding(tv.length, paddedSize));
} catch (IOException ioe) {
_log.error("Error writing data", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing data", dfe);
return null;
}
return encrypt(baos.toByteArray(), sessionKey, iv);
}
public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
if ( (iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16) )
return null;
byte decr[] = decrypt(payload, sessionKey, iv);
if (decr == null) {
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
return null;
}
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
try {
Hash rh = new Hash();
rh.readBytes(bais);
if (!h.equals(rh)) {
_log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length) + "]", new Exception("Hash error"));
return null;
}
long len = DataHelper.readLong(bais, 4);
byte data[] = new byte[(int)len];
int read = bais.read(data);
if (read != len) {
_log.error("Not enough to read");
return null;
}
return data;
} catch (IOException ioe) {
_log.error("Error writing data", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing data", dfe);
return null;
}
}
/** decrypt the data with the session key provided
* @param cyphertext encrypted data
* @param sessionKey private session key
* @param initializationVector IV for CBC
* @return unencrypted data
*/
public byte[] decrypt(byte cyphertext[], SessionKey sessionKey, byte initializationVector[]) {
if ( (initializationVector == null) || (cyphertext == null) || (sessionKey == null) || (initializationVector.length != 16) )
return null;
byte payload[] = new byte[cyphertext.length];
_log.warn("Warning: AES is disabled");
return cyphertext;
}
public static void main(String args[]) {
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
byte sbuf[] = new byte[16];
RandomSource.getInstance().nextBytes(sbuf);
byte se[] = AESEngine.getInstance().encrypt(sbuf, key, iv);
byte sd[] = AESEngine.getInstance().decrypt(se, key, iv);
_log.debug("Short test: " + DataHelper.eq(sd, sbuf));
byte lbuf[] = new byte[1024];
RandomSource.getInstance().nextBytes(sbuf);
byte le[] = AESEngine.getInstance().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = AESEngine.getInstance().safeDecrypt(le, key, iv);
_log.debug("Long test: " + DataHelper.eq(ld, lbuf));
}
}

View File

@ -0,0 +1,411 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import net.i2p.util.RandomSource;
/**
* This reads an underlying stream as written by AESOutputStream - AES256 encrypted
* in CBC mode with PKCS#5 padding, with the padding on each and every block of
* 16 bytes. This minimizes the overhead when communication is intermittent,
* rather than when streams of large sets of data are sent (in which case, the
* padding would be on a larger size - say, 1k, though in the worst case that
* would have 1023 bytes of padding, while in the worst case here, we only have
* 15 bytes of padding). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESInputStream extends FilterInputStream {
private final static Log _log = new Log(AESInputStream.class);
private final static CryptixAESEngine _engine = new CryptixAESEngine();
private SessionKey _key;
private byte[] _lastBlock;
private boolean _eofFound;
private long _cumulativeRead; // how many read from the source stream
private long _cumulativePrepared; // how many bytes decrypted and added to _readyBuf
private long _cumulativePaddingStripped; // how many bytes have been stripped
private ByteArrayOutputStream _encryptedBuf; // read from the stream but not yet decrypted
private List _readyBuf; // list of Bytes ready to be consumed, where index 0 is the first
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
private final static int READ_SIZE = BLOCK_SIZE;
private final static int DECRYPT_SIZE = BLOCK_SIZE-1;
public AESInputStream(InputStream source, SessionKey key, byte iv[]) {
super(source);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_encryptedBuf = new ByteArrayOutputStream(BLOCK_SIZE);
_readyBuf = new LinkedList();
_cumulativePaddingStripped = 0;
_eofFound = false;
}
public int read() throws IOException {
while ( (!_eofFound) && (_readyBuf.size() <= 0) ) {
refill(READ_SIZE);
}
Integer nval = getNext();
if (nval != null) {
return nval.intValue();
} else {
//_log.debug("No byte available. eof? " + _eofFound);
if (_eofFound)
return -1;
else {
throw new IOException("Not EOF, but none available? " + _readyBuf.size() + "/" + _encryptedBuf.size() + "/" + _cumulativeRead + "... impossible");
}
}
}
public int read(byte dest[]) throws IOException {
for (int i = 0; i < dest.length; i++) {
int val = read();
if (val == -1) {
// no more to read... can they expect more?
if (_eofFound && (i == 0))
return -1;
else
return i;
} else {
dest[i] = (byte)val;
}
}
_log.debug("Read the full buffer of size " + dest.length);
return dest.length;
}
public int read(byte dest[], int off, int len) throws IOException {
byte buf[] = new byte[len];
int read = read(buf);
if (read == -1)
return -1;
System.arraycopy(buf, 0, dest, off, read);
return read;
}
public long skip(long numBytes) throws IOException {
for (long l = 0; l < numBytes; l++) {
int val = read();
if (val == -1)
return l;
}
return numBytes;
}
public int available() throws IOException { return _readyBuf.size(); }
public void close() throws IOException {
//_log.debug("We have " + _encryptedBuf.size() + " available to decrypt... doing so");
//decrypt();
//byte buf[] = new byte[_readyBuf.size()];
//for (int i = 0; i < buf.length; i++)
// buf[i] = ((Integer)_readyBuf.get(i)).byteValue();
//_log.debug("After decrypt: readyBuf.size: " + _readyBuf.size() + "\n val:\t" + Base64.encode(buf));
int ready = _readyBuf.size();
int encrypted = _readyBuf.size();
_readyBuf.clear();
_encryptedBuf.reset();
in.close();
_log.debug("Cumulative bytes read from source/decrypted/stripped: " + _cumulativeRead + "/"+_cumulativePrepared +"/" + _cumulativePaddingStripped + "] remaining [" + ready + " ready, " + encrypted + " still encrypted]");
}
public void mark(int readLimit) {}
public void reset() throws IOException { throw new IOException("Reset not supported"); }
public boolean markSupported() { return false; }
/**
* Retrieve the next ready byte, or null if no bytes are ready. this does not refill or block
*
*/
private Integer getNext() {
if (_readyBuf.size() > 0) {
return (Integer)_readyBuf.remove(0);
} else {
return null;
}
}
/**
* Read at least one new byte from the underlying stream, and up to max new bytes,
* but not necessarily enough for a new decrypted block. This blocks until at least
* one new byte is read from the stream
*
*/
private void refill(int max) throws IOException {
if (!_eofFound) {
byte buf[] = new byte[max];
int read = in.read(buf);
if (read == -1) {
_eofFound = true;
} else if (read > 0) {
//_log.debug("Read from the source stream " + read + " bytes");
_cumulativeRead += read;
_encryptedBuf.write(buf, 0, read);
}
}
if (false) return; // true to keep the data for decrypt/display on close
if (_encryptedBuf.size() > 0) {
if (_encryptedBuf.size() >= DECRYPT_SIZE) {
//_log.debug("We have " + _encryptedBuf.size() + " available to decrypt... doing so");
decrypt();
//if (_encryptedBuf.size() > 0)
// _log.debug("Bytes left in the encrypted buffer after decrypt: " + _encryptedBuf.size());
} else {
if (_eofFound) {
//_log.debug("EOF and not enough bytes to decrypt [size = " + _encryptedBuf.size() + " totalCumulative: " + _cumulativeRead + "/"+_cumulativePrepared +"]!");
} else {
//_log.debug("Not enough bytes to decrypt [size = " + _encryptedBuf.size() + " totalCumulative: " + _cumulativeRead + "/"+_cumulativePrepared +"]");
}
}
}
}
/**
* Take (n*BLOCK_SIZE) bytes off the _encryptedBuf, decrypt them, and place
* them on _readyBuf
*
*/
private void decrypt() throws IOException {
byte encrypted[] = _encryptedBuf.toByteArray();
_encryptedBuf.reset();
if ( (encrypted == null) || (encrypted.length <= 0) )
throw new IOException("Error decrypting - no data to decrypt");
int numBlocks = encrypted.length / BLOCK_SIZE;
if ( (encrypted.length % BLOCK_SIZE) != 0) {
// it was flushed / handled off the BLOCK_SIZE segments, so put the excess
// back into the _encryptedBuf for later handling
int trailing = encrypted.length % BLOCK_SIZE;
_encryptedBuf.write(encrypted, encrypted.length - trailing, trailing);
byte nencrypted[] = new byte[encrypted.length - trailing];
System.arraycopy(encrypted, 0, nencrypted, 0, nencrypted.length);
encrypted = nencrypted;
_log.warn("Decrypt got odd segment - " + trailing + " bytes pushed back for later decryption - corrupted or slow data stream perhaps?");
} else {
//_log.info(encrypted.length + " bytes makes up " + numBlocks + " blocks to decrypt normally");
}
byte block[] = new byte[BLOCK_SIZE];
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(encrypted, i*BLOCK_SIZE, block, 0, BLOCK_SIZE);
byte decrypted[] = _engine.decrypt(block, _key, _lastBlock);
byte data[] = CryptixAESEngine.xor(decrypted, _lastBlock);
int cleaned[] = stripPadding(data);
for (int j = 0; j < cleaned.length; j++) {
if ( ((int)cleaned[j]) <= 0) {
cleaned[j] += 256;
//_log.error("(modified: " + cleaned[j] + ")");
}
_readyBuf.add(new Integer(cleaned[j]));
}
_cumulativePrepared += cleaned.length;
//_log.debug("Updating last block for inputStream");
System.arraycopy(decrypted, 0, _lastBlock, 0, BLOCK_SIZE);
}
int remaining = encrypted.length % BLOCK_SIZE;
if (remaining != 0) {
_encryptedBuf.write(encrypted, encrypted.length-remaining, remaining);
_log.debug("After pushing " + remaining + " bytes back onto the buffer, lets delay 1s our action so we don't fast busy until the net transfers data");
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
} else {
//_log.debug("No remaining encrypted bytes beyond the block size");
}
}
/**
* PKCS#5 specifies the padding for the block has the # of padding bytes
* located in the last byte of the block, and each of the padding bytes are
* equal to that value.
* e.g. in a 4 byte block:
* 0x0a padded would become
* 0x0a 0x03 0x03 0x03
* e.g. in a 4 byte block:
* 0x01 0x02 padded would become
* 0x01 0x02 0x02 0x02
*
* We use 16 byte blocks in this AES implementation
*
*/
private int[] stripPadding(byte data[]) throws IOException {
int numPadBytes = (int)data[data.length-1];
if ( (numPadBytes >= data.length) || (numPadBytes <= 0) )
throw new IOException("Invalid number of pad bytes");
int rv[] = new int[data.length-numPadBytes];
// optional, but a really good idea: verify the padding
if (true) {
for (int i = data.length - numPadBytes; i < data.length; i++) {
if (data[i] != (byte)numPadBytes) {
throw new IOException("Incorrect padding on decryption: data["+i+"] = " + data[i] + " not " + numPadBytes);
}
}
}
for (int i = 0; i < rv.length; i++)
rv[i] = data[i];
_cumulativePaddingStripped += numPadBytes;
return rv;
}
int remainingBytes() { return _encryptedBuf.size(); }
int readyBytes() { return _readyBuf.size(); }
/**
* Test AESOutputStream/AESInputStream
*/
public static void main(String args[]) {
byte orig[] = new byte[1024*32];
RandomSource.getInstance().nextBytes(orig);
//byte orig[] = "you are my sunshine, my only sunshine".getBytes();
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = "there once was a".getBytes();
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
}
_log.info("Done testing 32KB data");
orig = new byte[20];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
}
_log.info("Done testing 20 byte data");
orig = new byte[3];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
}
_log.info("Done testing 3 byte data");
orig = new byte[0];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
}
_log.info("Done testing 0 byte data");
orig = new byte[32];
RandomSource.getInstance().nextBytes(orig);
runOffsetTest(orig, key, iv);
_log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
private static void runTest(byte orig[], SessionKey key, byte[] iv) {
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024*32];
int read = DataHelper.read(in, buf);
if (read > 0)
baos.write(buf, 0, read);
in.close();
byte fin[] = baos.toByteArray();
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
_log.info("Equal hashes. hash: " + origHash);
else
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (Throwable t) {
_log.error("ERROR transferring", t);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
private static void runOffsetTest(byte orig[], SessionKey key, byte[] iv) {
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
_log.info("Encrypted segment length: " + encrypted.length);
byte encryptedSegment[] = new byte[40];
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024*32];
int read = DataHelper.read(in, buf);
int remaining = in.remainingBytes();
int readyBytes = in.readyBytes();
_log.info("Read: " + read);
if (read > 0)
baos.write(buf, 0, read);
in.close();
byte fin[] = baos.toByteArray();
_log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
_log.info("Equal hashes. hash: " + origHash);
else
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (Throwable t) {
_log.error("ERROR transferring", t);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
}

View File

@ -0,0 +1,125 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
/**
* This writes everything as CBC with PKCS#5 padding, but each block is padded
* so as soon as a block is received it can be decrypted (rather than wait for
* an arbitrary number of blocks to arrive). That means that each block sent
* will contain exactly one padding byte (unless it was flushed with
* numBytes % (BLOCK_SIZE-1) != 0, in which case that last block will be padded
* with up to 15 bytes). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESOutputStream extends FilterOutputStream {
private final static CryptixAESEngine _engine = new CryptixAESEngine();
private final static Log _log = new Log(AESOutputStream.class);
private SessionKey _key;
private byte[] _lastBlock;
private ByteArrayOutputStream _inBuf;
private long _cumulativeProvided; // how many bytes provided to this stream
private long _cumulativeWritten; // how many bytes written to the underlying stream
private long _cumulativePadding; // how many bytes of padding written
public final static float EXPANSION_FACTOR = 1.0625f; // 6% overhead w/ the padding
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
private final static int MAX_BUF = 256;
public AESOutputStream(OutputStream source, SessionKey key, byte[] iv) {
super(source);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_inBuf = new ByteArrayOutputStream(MAX_BUF);
}
public void write(int val) throws IOException {
_cumulativeProvided++;
_inBuf.write(val);
if (_inBuf.size() > MAX_BUF)
doFlush();
}
public void write(byte src[]) throws IOException {
_cumulativeProvided += src.length;
_inBuf.write(src);
if (_inBuf.size() > MAX_BUF)
doFlush();
}
public void write(byte src[], int off, int len) throws IOException {
_cumulativeProvided += len;
_inBuf.write(src, off, len);
if (_inBuf.size() > MAX_BUF)
doFlush();
}
public void close() throws IOException {
flush();
out.close();
_inBuf.reset();
_log.debug("Cumulative bytes provided to this stream / written out / padded: " + _cumulativeProvided + "/" + _cumulativeWritten + "/" + _cumulativePadding);
}
public void flush() throws IOException { doFlush(); out.flush(); }
private void doFlush() throws IOException {
writeEncrypted(_inBuf.toByteArray());
_inBuf.reset();
}
/**
* Encrypt an arbitrary size array with AES using CBC and PKCS#5 padding,
* write it to the stream, and set _lastBlock to the last encrypted
* block. This operation works by taking every (BLOCK_SIZE-1) bytes
* from the src, padding it with PKCS#5 (aka adding 0x01), and encrypting
* it. If the last block doesn't contain exactly (BLOCK_SIZE-1) bytes, it
* is padded with PKCS#5 as well (adding # padding bytes repeated that many
* times).
*
*/
private void writeEncrypted(byte src[]) throws IOException {
if ( (src == null) || (src.length == 0) ) return;
int numBlocks = src.length/(BLOCK_SIZE-1);
byte block[] = new byte[BLOCK_SIZE];
block[BLOCK_SIZE-1] = 0x01; // the padding byte for "full" blocks
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(src, i*15, block, 0, 15);
byte data[] = _engine.xor(block, _lastBlock);
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
_cumulativeWritten += encrypted.length;
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length-BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding++;
}
if (src.length % 15 != 0) {
// we need to do non trivial padding
int remainingBytes = src.length-numBlocks*15;
int paddingBytes = BLOCK_SIZE - remainingBytes;
System.arraycopy(src, numBlocks*15, block, 0, remainingBytes);
Arrays.fill(block, remainingBytes, BLOCK_SIZE, (byte)paddingBytes);
byte data[] = _engine.xor(block, _lastBlock);
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length-BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding += paddingBytes;
_cumulativeWritten += encrypted.length;
}
}
}

View File

@ -0,0 +1,150 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.security.InvalidKeyException;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
/**
* Wrapper for AES cypher operation using Cryptix's Rijndael implementation. Implements
* CBC with a 16 byte IV.
* Problems:
* Only supports data of size mod 16 bytes - no inherent padding.
*
* @author jrandom, thecrypto
* @license GPL
*/
public class CryptixAESEngine extends AESEngine {
private final static Log _log = new Log(CryptixAESEngine.class);
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false;
private final static byte FAKE_KEY = 0x2A;
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ( (initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null) || (initializationVector.length != 16) )
return null;
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
byte rv[] = new byte[payload.length];
for (int i = 0; i < rv.length; i++)
rv[i] = (byte)(payload[i] ^ FAKE_KEY);
return rv;
}
int numblock = payload.length/16;
if (payload.length % 16 != 0)
numblock++;
byte[][] plain = new byte[numblock][16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
plain[x][y] = payload[x*16+y];
}
}
byte[][] cipher = new byte[numblock][16];
cipher[0] = encrypt(xor(initializationVector, plain[0]), sessionKey);
for (int x = 1; x < numblock; x++) {
cipher[x] = encrypt(xor(cipher[x-1], plain[x]), sessionKey);
}
byte[] ret = new byte[numblock * 16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
ret[x*16+y] = cipher[x][y];
}
}
return ret;
}
public byte[] decrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ( (initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null) || (initializationVector.length != 16) )
return null;
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
byte rv[] = new byte[payload.length];
for (int i = 0; i < rv.length; i++)
rv[i] = (byte)(payload[i] ^ FAKE_KEY);
return rv;
}
int numblock = payload.length/16;
if (payload.length % 16 != 0)
numblock++;
byte[][] cipher = new byte[numblock][16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
cipher[x][y] = payload[x*16+y];
}
}
byte[][] plain = new byte[numblock][16];
plain[0] = xor(decrypt(cipher[0], sessionKey), initializationVector);
for (int x = 1; x < numblock; x++) {
plain[x] = xor(decrypt(cipher[x], sessionKey), cipher[x-1]);
}
byte[] ret = new byte[numblock * 16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
ret[x*16+y] = plain[x][y];
}
}
return ret;
}
final static byte[] xor (byte[] a, byte[] b) {
if ( (a == null) || (b == null) || (a.length != b.length) )
return null;
byte[] ret = new byte[a.length];
for (int x = 0; x < a.length; x++) {
ret[x] = (byte)(a[x] ^ b[x]);
}
return ret;
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param sessionKey private esession key to encrypt to
* @return encrypted data
*/
final static byte[] encrypt(byte payload[], SessionKey sessionKey) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];
CryptixRijndael_Algorithm.blockEncrypt(payload, rv, 0, key, 16);
return rv;
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
return null;
}
}
/** decrypt the data with the session key provided
* @param payload encrypted data
* @param sessionKey private session key
* @return unencrypted data
*/
final static byte[] decrypt(byte payload[], SessionKey sessionKey) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, 0, key, 16);
return rv;
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
return null;
}
}
}

View File

@ -0,0 +1,874 @@
/*
* Copyright (c) 1997, 1998 Systemics Ltd on behalf of
* the Cryptix Development Team. All rights reserved.
*/
package net.i2p.crypto;
import net.i2p.util.Clock;
import java.io.PrintWriter;
import java.security.InvalidKeyException;
//...........................................................................
/**
* Rijndael --pronounced Reindaal-- is a variable block-size (128-, 192- and
* 256-bit), variable key-size (128-, 192- and 256-bit) symmetric cipher.<p>
*
* Rijndael was written by <a href="mailto:rijmen@esat.kuleuven.ac.be">Vincent
* Rijmen</a> and <a href="mailto:Joan.Daemen@village.uunet.be">Joan Daemen</a>.<p>
*
* Portions of this code are <b>Copyright</b> &copy; 1997, 1998
* <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the
* <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
* <br>All rights reserved.<p>
*
* @author Raif S. Naffah
* @author Paulo S. L. M. Barreto
*
* License is apparently available from http://www.cryptix.org/docs/license.html
*/
public final class CryptixRijndael_Algorithm // implicit no-argument constructor
{
// Debugging methods and variables
//...........................................................................
static final String _NAME = "Rijndael_Algorithm";
static final boolean _IN = true, _OUT = false;
static final boolean _RDEBUG = false;
static final int _debuglevel = 0; // RDEBUG ? Rijndael_Properties.getLevel(NAME): 0;
// static final PrintWriter err = RDEBUG ? Rijndael_Properties.getOutput() : null;
static final PrintWriter _err = new PrintWriter(new java.io.OutputStreamWriter(System.err));
static final boolean _TRACE = false; // Rijndael_Properties.isTraceable(NAME);
static void debug (String s) { _err.println(">>> "+_NAME+": "+s); }
static void trace (boolean in, String s) {
if (_TRACE) _err.println((in?"==> ":"<== ")+_NAME+"."+s);
}
static void trace (String s) { if (_TRACE) _err.println("<=> "+_NAME+"."+s); }
// Constants and variables
//...........................................................................
static final int _BLOCK_SIZE = 16; // default block size in bytes
static final int[] _alog = new int[256];
static final int[] _log = new int[256];
static final byte[] _S = new byte[256];
static final byte[] _Si = new byte[256];
static final int[] _T1 = new int[256];
static final int[] _T2 = new int[256];
static final int[] _T3 = new int[256];
static final int[] _T4 = new int[256];
static final int[] _T5 = new int[256];
static final int[] _T6 = new int[256];
static final int[] _T7 = new int[256];
static final int[] _T8 = new int[256];
static final int[] _U1 = new int[256];
static final int[] _U2 = new int[256];
static final int[] _U3 = new int[256];
static final int[] _U4 = new int[256];
static final byte[] _rcon = new byte[30];
static final int[][][] _shifts = new int[][][] {
{ {0, 0}, {1, 3}, {2, 2}, {3, 1} },
{ {0, 0}, {1, 5}, {2, 4}, {3, 3} },
{ {0, 0}, {1, 7}, {3, 5}, {4, 4} }
};
private static final char[] _HEX_DIGITS = {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
// Static code - to intialise S-boxes and T-boxes
//...........................................................................
static {
long time = Clock.getInstance().now();
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Algorithm Name: Rijndael ver 0.1");
System.out.println("Electronic Codebook (ECB) Mode");
System.out.println();
}
int ROOT = 0x11B;
int i, j = 0;
//
// produce log and alog tables, needed for multiplying in the
// field GF(2^m) (generator = 3)
//
_alog[0] = 1;
for (i = 1; i < 256; i++) {
j = (_alog[i-1] << 1) ^ _alog[i-1];
if ((j & 0x100) != 0) j ^= ROOT;
_alog[i] = j;
}
for (i = 1; i < 255; i++) _log[_alog[i]] = i;
byte[][] A = new byte[][] {
{1, 1, 1, 1, 1, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 1, 1, 1, 1},
{1, 1, 0, 0, 0, 1, 1, 1},
{1, 1, 1, 0, 0, 0, 1, 1},
{1, 1, 1, 1, 0, 0, 0, 1}
};
byte[] B = new byte[] { 0, 1, 1, 0, 0, 0, 1, 1};
//
// substitution box based on F^{-1}(x)
//
int t;
byte[][] box = new byte[256][8];
box[1][7] = 1;
for (i = 2; i < 256; i++) {
j = _alog[255 - _log[i]];
for (t = 0; t < 8; t++)
box[i][t] = (byte)((j >>> (7 - t)) & 0x01);
}
//
// affine transform: box[i] <- B + A*box[i]
//
byte[][] cox = new byte[256][8];
for (i = 0; i < 256; i++)
for (t = 0; t < 8; t++) {
cox[i][t] = B[t];
for (j = 0; j < 8; j++)
cox[i][t] ^= A[t][j] * box[i][j];
}
//
// S-boxes and inverse S-boxes
//
for (i = 0; i < 256; i++) {
_S[i] = (byte)(cox[i][0] << 7);
for (t = 1; t < 8; t++)
_S[i] ^= cox[i][t] << (7-t);
_Si[_S[i] & 0xFF] = (byte) i;
}
//
// T-boxes
//
byte[][] G = new byte[][] {
{2, 1, 1, 3},
{3, 2, 1, 1},
{1, 3, 2, 1},
{1, 1, 3, 2}
};
byte[][] AA = new byte[4][8];
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) AA[i][j] = G[i][j];
AA[i][i+4] = 1;
}
byte pivot, tmp;
byte[][] iG = new byte[4][4];
for (i = 0; i < 4; i++) {
pivot = AA[i][i];
if (pivot == 0) {
t = i + 1;
while ((AA[t][i] == 0) && (t < 4))
t++;
if (t == 4)
throw new RuntimeException("G matrix is not invertible");
else {
for (j = 0; j < 8; j++) {
tmp = AA[i][j];
AA[i][j] = AA[t][j];
AA[t][j] = (byte) tmp;
}
pivot = AA[i][i];
}
}
for (j = 0; j < 8; j++)
if (AA[i][j] != 0)
AA[i][j] = (byte)
_alog[(255 + _log[AA[i][j] & 0xFF] - _log[pivot & 0xFF]) % 255];
for (t = 0; t < 4; t++)
if (i != t) {
for (j = i+1; j < 8; j++)
AA[t][j] ^= mul(AA[i][j], AA[t][i]);
AA[t][i] = 0;
}
}
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++) iG[i][j] = AA[i][j + 4];
int s;
for (t = 0; t < 256; t++) {
s = _S[t];
_T1[t] = mul4(s, G[0]);
_T2[t] = mul4(s, G[1]);
_T3[t] = mul4(s, G[2]);
_T4[t] = mul4(s, G[3]);
s = _Si[t];
_T5[t] = mul4(s, iG[0]);
_T6[t] = mul4(s, iG[1]);
_T7[t] = mul4(s, iG[2]);
_T8[t] = mul4(s, iG[3]);
_U1[t] = mul4(t, iG[0]);
_U2[t] = mul4(t, iG[1]);
_U3[t] = mul4(t, iG[2]);
_U4[t] = mul4(t, iG[3]);
}
//
// round constants
//
_rcon[0] = 1;
int r = 1;
for (t = 1; t < 30; ) _rcon[t++] = (byte)(r = mul(2, r));
time = Clock.getInstance().now() - time;
if (_RDEBUG && _debuglevel > 8) {
System.out.println("==========");
System.out.println();
System.out.println("Static Data");
System.out.println();
System.out.println("S[]:"); for(i=0;i<16;i++) { for(j=0;j<16;j++) System.out.print("0x"+byteToString(_S[i*16+j])+", "); System.out.println();}
System.out.println();
System.out.println("Si[]:"); for(i=0;i<16;i++) { for(j=0;j<16;j++) System.out.print("0x"+byteToString(_Si[i*16+j])+", "); System.out.println();}
System.out.println();
System.out.println("iG[]:"); for(i=0;i<4;i++){for(j=0;j<4;j++) System.out.print("0x"+byteToString(iG[i][j])+", "); System.out.println();}
System.out.println();
System.out.println("T1[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T1[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T2[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T2[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T3[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T3[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T4[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T4[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T5[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T5[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T6[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T6[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T7[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T7[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("T8[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_T8[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("U1[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_U1[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("U2[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_U2[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("U3[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_U3[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("U4[]:"); for(i=0;i<64;i++){for(j=0;j<4;j++) System.out.print("0x"+intToString(_U4[i*4+j])+", "); System.out.println();}
System.out.println();
System.out.println("rcon[]:"); for(i=0;i<5;i++){for(j=0;j<6;j++) System.out.print("0x"+byteToString(_rcon[i*6+j])+", "); System.out.println();}
System.out.println();
System.out.println("Total initialization time: "+time+" ms.");
System.out.println();
}
}
// multiply two elements of GF(2^m)
static final int mul (int a, int b) {
return (a != 0 && b != 0) ?
_alog[(_log[a & 0xFF] + _log[b & 0xFF]) % 255] :
0;
}
// convenience method used in generating Transposition boxes
static final int mul4 (int a, byte[] b) {
if (a == 0) return 0;
a = _log[a & 0xFF];
int a0 = (b[0] != 0) ? _alog[(a + _log[b[0] & 0xFF]) % 255] & 0xFF : 0;
int a1 = (b[1] != 0) ? _alog[(a + _log[b[1] & 0xFF]) % 255] & 0xFF : 0;
int a2 = (b[2] != 0) ? _alog[(a + _log[b[2] & 0xFF]) % 255] & 0xFF : 0;
int a3 = (b[3] != 0) ? _alog[(a + _log[b[3] & 0xFF]) % 255] & 0xFF : 0;
return a0 << 24 | a1 << 16 | a2 << 8 | a3;
}
// Basic API methods
//...........................................................................
/**
* Convenience method to expand a user-supplied key material into a
* session key, assuming Rijndael's default block size (128-bit).
*
* @param k The 128/192/256-bit user-key to use.
* @exception InvalidKeyException If the key is invalid.
*/
public static final Object makeKey (byte[] k) throws InvalidKeyException {
return makeKey(k, _BLOCK_SIZE);
}
/**
* Convenience method to encrypt exactly one block of plaintext, assuming
* Rijndael's default block size (128-bit).
*
* @param in The plaintext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for encryption.
* @return The ciphertext generated from a plaintext using the session key.
*/
public static final void
blockEncrypt (byte[] in, byte[] result, int inOffset, Object sessionKey) {
if (_RDEBUG) trace(_IN, "blockEncrypt("+in+", "+inOffset+", "+sessionKey+")");
int[][] Ke = (int[][]) ((Object[]) sessionKey)[0]; // extract encryption round keys
int ROUNDS = Ke.length - 1;
int[] Ker = Ke[0];
// plaintext to ints + key
int t0 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Ker[0];
int t1 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Ker[1];
int t2 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Ker[2];
int t3 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Ker[3];
int a0, a1, a2, a3;
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
Ker = Ke[r];
a0 = (_T1[(t0 >>> 24) & 0xFF] ^
_T2[(t1 >>> 16) & 0xFF] ^
_T3[(t2 >>> 8) & 0xFF] ^
_T4[ t3 & 0xFF] ) ^ Ker[0];
a1 = (_T1[(t1 >>> 24) & 0xFF] ^
_T2[(t2 >>> 16) & 0xFF] ^
_T3[(t3 >>> 8) & 0xFF] ^
_T4[ t0 & 0xFF] ) ^ Ker[1];
a2 = (_T1[(t2 >>> 24) & 0xFF] ^
_T2[(t3 >>> 16) & 0xFF] ^
_T3[(t0 >>> 8) & 0xFF] ^
_T4[ t1 & 0xFF] ) ^ Ker[2];
a3 = (_T1[(t3 >>> 24) & 0xFF] ^
_T2[(t0 >>> 16) & 0xFF] ^
_T3[(t1 >>> 8) & 0xFF] ^
_T4[ t2 & 0xFF] ) ^ Ker[3];
t0 = a0;
t1 = a1;
t2 = a2;
t3 = a3;
if (_RDEBUG && _debuglevel > 6) System.out.println("CT"+r+"="+intToString(t0)+intToString(t1)+intToString(t2)+intToString(t3));
}
// last round is special
Ker = Ke[ROUNDS];
int tt = Ker[0];
result[ 0] = (byte)(_S[(t0 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 1] = (byte)(_S[(t1 >>> 16) & 0xFF] ^ (tt >>> 16));
result[ 2] = (byte)(_S[(t2 >>> 8) & 0xFF] ^ (tt >>> 8));
result[ 3] = (byte)(_S[ t3 & 0xFF] ^ tt );
tt = Ker[1];
result[ 4] = (byte)(_S[(t1 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 5] = (byte)(_S[(t2 >>> 16) & 0xFF] ^ (tt >>> 16));
result[ 6] = (byte)(_S[(t3 >>> 8) & 0xFF] ^ (tt >>> 8));
result[ 7] = (byte)(_S[ t0 & 0xFF] ^ tt );
tt = Ker[2];
result[ 8] = (byte)(_S[(t2 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 9] = (byte)(_S[(t3 >>> 16) & 0xFF] ^ (tt >>> 16));
result[10] = (byte)(_S[(t0 >>> 8) & 0xFF] ^ (tt >>> 8));
result[11] = (byte)(_S[ t1 & 0xFF] ^ tt );
tt = Ker[3];
result[12] = (byte)(_S[(t3 >>> 24) & 0xFF] ^ (tt >>> 24));
result[13] = (byte)(_S[(t0 >>> 16) & 0xFF] ^ (tt >>> 16));
result[14] = (byte)(_S[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[15] = (byte)(_S[ t2 & 0xFF] ^ tt );
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT="+toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
}
/**
* Convenience method to decrypt exactly one block of plaintext, assuming
* Rijndael's default block size (128-bit).
*
* @param in The ciphertext.
* @param result the resulting ciphertext
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for decryption.
* @return The plaintext generated from a ciphertext using the session key.
*/
public static final void
blockDecrypt (byte[] in, byte[] result, int inOffset, Object sessionKey) {
if (_RDEBUG) trace(_IN, "blockDecrypt("+in+", "+inOffset+", "+sessionKey+")");
int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys
int ROUNDS = Kd.length - 1;
int[] Kdr = Kd[0];
// ciphertext to ints + key
int t0 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Kdr[0];
int t1 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Kdr[1];
int t2 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Kdr[2];
int t3 = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Kdr[3];
int a0, a1, a2, a3;
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
Kdr = Kd[r];
a0 = (_T5[(t0 >>> 24) & 0xFF] ^
_T6[(t3 >>> 16) & 0xFF] ^
_T7[(t2 >>> 8) & 0xFF] ^
_T8[ t1 & 0xFF] ) ^ Kdr[0];
a1 = (_T5[(t1 >>> 24) & 0xFF] ^
_T6[(t0 >>> 16) & 0xFF] ^
_T7[(t3 >>> 8) & 0xFF] ^
_T8[ t2 & 0xFF] ) ^ Kdr[1];
a2 = (_T5[(t2 >>> 24) & 0xFF] ^
_T6[(t1 >>> 16) & 0xFF] ^
_T7[(t0 >>> 8) & 0xFF] ^
_T8[ t3 & 0xFF] ) ^ Kdr[2];
a3 = (_T5[(t3 >>> 24) & 0xFF] ^
_T6[(t2 >>> 16) & 0xFF] ^
_T7[(t1 >>> 8) & 0xFF] ^
_T8[ t0 & 0xFF] ) ^ Kdr[3];
t0 = a0;
t1 = a1;
t2 = a2;
t3 = a3;
if (_RDEBUG && _debuglevel > 6) System.out.println("PT"+r+"="+intToString(t0)+intToString(t1)+intToString(t2)+intToString(t3));
}
// last round is special
Kdr = Kd[ROUNDS];
int tt = Kdr[0];
result[ 0] = (byte)(_Si[(t0 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 1] = (byte)(_Si[(t3 >>> 16) & 0xFF] ^ (tt >>> 16));
result[ 2] = (byte)(_Si[(t2 >>> 8) & 0xFF] ^ (tt >>> 8));
result[ 3] = (byte)(_Si[ t1 & 0xFF] ^ tt );
tt = Kdr[1];
result[ 4] = (byte)(_Si[(t1 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 5] = (byte)(_Si[(t0 >>> 16) & 0xFF] ^ (tt >>> 16));
result[ 6] = (byte)(_Si[(t3 >>> 8) & 0xFF] ^ (tt >>> 8));
result[ 7] = (byte)(_Si[ t2 & 0xFF] ^ tt );
tt = Kdr[2];
result[ 8] = (byte)(_Si[(t2 >>> 24) & 0xFF] ^ (tt >>> 24));
result[ 9] = (byte)(_Si[(t1 >>> 16) & 0xFF] ^ (tt >>> 16));
result[10] = (byte)(_Si[(t0 >>> 8) & 0xFF] ^ (tt >>> 8));
result[11] = (byte)(_Si[ t3 & 0xFF] ^ tt );
tt = Kdr[3];
result[12] = (byte)(_Si[(t3 >>> 24) & 0xFF] ^ (tt >>> 24));
result[13] = (byte)(_Si[(t2 >>> 16) & 0xFF] ^ (tt >>> 16));
result[14] = (byte)(_Si[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[15] = (byte)(_Si[ t0 & 0xFF] ^ tt );
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT="+toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
}
/** A basic symmetric encryption/decryption test. */
public static boolean self_test() { return self_test(_BLOCK_SIZE); }
// Rijndael own methods
//...........................................................................
/** @return The default length in bytes of the Algorithm input block. */
public static final int blockSize() { return _BLOCK_SIZE; }
/**
* Expand a user-supplied key material into a session key.
*
* @param k The 128/192/256-bit user-key to use.
* @param blockSize The block size in bytes of this Rijndael.
* @exception InvalidKeyException If the key is invalid.
*/
public static final /* synchronized */ Object makeKey (byte[] k, int blockSize)
throws InvalidKeyException {
if (_RDEBUG) trace(_IN, "makeKey("+k+", "+blockSize+")");
if (k == null)
throw new InvalidKeyException("Empty key");
if (!(k.length == 16 || k.length == 24 || k.length == 32))
throw new InvalidKeyException("Incorrect key length");
int ROUNDS = getRounds(k.length, blockSize);
int BC = blockSize / 4;
int[][] Ke = new int[ROUNDS + 1][BC]; // encryption round keys
int[][] Kd = new int[ROUNDS + 1][BC]; // decryption round keys
int ROUND_KEY_COUNT = (ROUNDS + 1) * BC;
int KC = k.length / 4;
int[] tk = new int[KC];
int i, j;
// copy user material bytes into temporary ints
for (i = 0, j = 0; i < KC; )
tk[i++] = (k[j++] & 0xFF) << 24 |
(k[j++] & 0xFF) << 16 |
(k[j++] & 0xFF) << 8 |
(k[j++] & 0xFF);
// copy values into round key arrays
int t = 0;
for (j = 0; (j < KC) && (t < ROUND_KEY_COUNT); j++, t++) {
Ke[t / BC][t % BC] = tk[j];
Kd[ROUNDS - (t / BC)][t % BC] = tk[j];
}
int tt, rconpointer = 0;
while (t < ROUND_KEY_COUNT) {
// extrapolate using phi (the round key evolution function)
tt = tk[KC - 1];
tk[0] ^= (_S[(tt >>> 16) & 0xFF] & 0xFF) << 24 ^
(_S[(tt >>> 8) & 0xFF] & 0xFF) << 16 ^
(_S[ tt & 0xFF] & 0xFF) << 8 ^
(_S[(tt >>> 24) & 0xFF] & 0xFF) ^
(_rcon[rconpointer++] & 0xFF) << 24;
if (KC != 8)
for (i = 1, j = 0; i < KC; ) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
else {
for (i = 1, j = 0; i < KC / 2; ) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
tt = tk[KC / 2 - 1];
tk[KC / 2] ^= (_S[ tt & 0xFF] & 0xFF) ^
(_S[(tt >>> 8) & 0xFF] & 0xFF) << 8 ^
(_S[(tt >>> 16) & 0xFF] & 0xFF) << 16 ^
(_S[(tt >>> 24) & 0xFF] & 0xFF) << 24;
for (j = KC / 2, i = j + 1; i < KC; ) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
}
// copy values into round key arrays
for (j = 0; (j < KC) && (t < ROUND_KEY_COUNT); j++, t++) {
Ke[t / BC][t % BC] = tk[j];
Kd[ROUNDS - (t / BC)][t % BC] = tk[j];
}
}
for (int r = 1; r < ROUNDS; r++) // inverse MixColumn where needed
for (j = 0; j < BC; j++) {
tt = Kd[r][j];
Kd[r][j] = _U1[(tt >>> 24) & 0xFF] ^
_U2[(tt >>> 16) & 0xFF] ^
_U3[(tt >>> 8) & 0xFF] ^
_U4[ tt & 0xFF];
}
// assemble the encryption (Ke) and decryption (Kd) round keys into
// one sessionKey object
Object[] sessionKey = new Object[] {Ke, Kd};
if (_RDEBUG) trace(_OUT, "makeKey()");
return sessionKey;
}
/**
* Encrypt exactly one block of plaintext.
*
* @param in The plaintext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for encryption.
* @param blockSize The block size in bytes of this Rijndael.
* @return The ciphertext generated from a plaintext using the session key.
*/
public static final void
blockEncrypt (byte[] in, byte[] result, int inOffset, Object sessionKey, int blockSize) {
if (blockSize == _BLOCK_SIZE) {
blockEncrypt(in, result, inOffset, sessionKey);
return;
}
if (_RDEBUG) trace(_IN, "blockEncrypt("+in+", "+inOffset+", "+sessionKey+", "+blockSize+")");
Object[] sKey = (Object[]) sessionKey; // extract encryption round keys
int[][] Ke = (int[][]) sKey[0];
int BC = blockSize / 4;
int ROUNDS = Ke.length - 1;
int SC = BC == 4 ? 0 : (BC == 6 ? 1 : 2);
int s1 = _shifts[SC][1][0];
int s2 = _shifts[SC][2][0];
int s3 = _shifts[SC][3][0];
int[] a = new int[BC];
int[] t = new int[BC]; // temporary work array
int i;
int j = 0, tt;
for (i = 0; i < BC; i++) // plaintext to ints + key
t[i] = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Ke[0][i];
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
for (i = 0; i < BC; i++)
a[i] = (_T1[(t[ i ] >>> 24) & 0xFF] ^
_T2[(t[(i + s1) % BC] >>> 16) & 0xFF] ^
_T3[(t[(i + s2) % BC] >>> 8) & 0xFF] ^
_T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("CT"+r+"="+toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Ke[ROUNDS][i];
result[j++] = (byte)(_S[(t[ i ] >>> 24) & 0xFF] ^ (tt >>> 24));
result[j++] = (byte)(_S[(t[(i + s1) % BC] >>> 16) & 0xFF] ^ (tt >>> 16));
result[j++] = (byte)(_S[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte)(_S[ t[(i + s3) % BC] & 0xFF] ^ tt);
}
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT="+toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
}
/**
* Decrypt exactly one block of ciphertext.
*
* @param in The ciphertext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for decryption.
* @param blockSize The block size in bytes of this Rijndael.
* @return The plaintext generated from a ciphertext using the session key.
*/
public static final void
blockDecrypt (byte[] in, byte[] result, int inOffset, Object sessionKey, int blockSize) {
if (blockSize == _BLOCK_SIZE) {
blockDecrypt(in, result, inOffset, sessionKey);
return;
}
if (_RDEBUG) trace(_IN, "blockDecrypt("+in+", "+inOffset+", "+sessionKey+", "+blockSize+")");
Object[] sKey = (Object[]) sessionKey; // extract decryption round keys
int[][] Kd = (int[][]) sKey[1];
int BC = blockSize / 4;
int ROUNDS = Kd.length - 1;
int SC = BC == 4 ? 0 : (BC == 6 ? 1 : 2);
int s1 = _shifts[SC][1][1];
int s2 = _shifts[SC][2][1];
int s3 = _shifts[SC][3][1];
int[] a = new int[BC];
int[] t = new int[BC]; // temporary work array
int i;
int j = 0, tt;
for (i = 0; i < BC; i++) // ciphertext to ints + key
t[i] = ((in[inOffset++] & 0xFF) << 24 |
(in[inOffset++] & 0xFF) << 16 |
(in[inOffset++] & 0xFF) << 8 |
(in[inOffset++] & 0xFF) ) ^ Kd[0][i];
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
for (i = 0; i < BC; i++)
a[i] = (_T5[(t[ i ] >>> 24) & 0xFF] ^
_T6[(t[(i + s1) % BC] >>> 16) & 0xFF] ^
_T7[(t[(i + s2) % BC] >>> 8) & 0xFF] ^
_T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("PT"+r+"="+toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Kd[ROUNDS][i];
result[j++] = (byte)(_Si[(t[ i ] >>> 24) & 0xFF] ^ (tt >>> 24));
result[j++] = (byte)(_Si[(t[(i + s1) % BC] >>> 16) & 0xFF] ^ (tt >>> 16));
result[j++] = (byte)(_Si[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte)(_Si[ t[(i + s3) % BC] & 0xFF] ^ tt);
}
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT="+toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
}
/** A basic symmetric encryption/decryption test for a given key size. */
private static boolean self_test (int keysize) {
if (_RDEBUG) trace(_IN, "self_test("+keysize+")");
boolean ok = false;
try {
byte[] kb = new byte[keysize];
byte[] pt = new byte[_BLOCK_SIZE];
int i;
for (i = 0; i < keysize; i++)
kb[i] = (byte) i;
for (i = 0; i < _BLOCK_SIZE; i++)
pt[i] = (byte) i;
if (_RDEBUG && _debuglevel > 6) {
System.out.println("==========");
System.out.println();
System.out.println("KEYSIZE="+(8*keysize));
System.out.println("KEY="+toString(kb));
System.out.println();
}
Object key = makeKey(kb, _BLOCK_SIZE);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Intermediate Ciphertext Values (Encryption)");
System.out.println();
System.out.println("PT="+toString(pt));
}
byte[] ct = new byte[_BLOCK_SIZE];
blockEncrypt(pt, ct, 0, key, _BLOCK_SIZE);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Intermediate Plaintext Values (Decryption)");
System.out.println();
System.out.println("CT="+toString(ct));
}
byte[] cpt = new byte[_BLOCK_SIZE];
blockDecrypt(ct, cpt, 0, key, _BLOCK_SIZE);
ok = areEqual(pt, cpt);
if (!ok)
throw new RuntimeException("Symmetric operation failed");
}
catch (Exception x) {
if (_RDEBUG && _debuglevel > 0) {
debug("Exception encountered during self-test: " + x.getMessage());
x.printStackTrace();
}
}
if (_RDEBUG && _debuglevel > 0) debug("Self-test OK? " + ok);
if (_RDEBUG) trace(_OUT, "self_test()");
return ok;
}
/**
* Return The number of rounds for a given Rijndael's key and block sizes.
*
* @param keySize The size of the user key material in bytes.
* @param blockSize The desired block size in bytes.
* @return The number of rounds for a given Rijndael's key and
* block sizes.
*/
public static final int getRounds (int keySize, int blockSize) {
switch (keySize) {
case 16:
return blockSize == 16 ? 10 : (blockSize == 24 ? 12 : 14);
case 24:
return blockSize != 32 ? 12 : 14;
default: // 32 bytes = 256 bits
return 14;
}
}
// utility static methods (from cryptix.util.core ArrayUtil and Hex classes)
//...........................................................................
/**
* Compares two byte arrays for equality.
*
* @return true if the arrays have identical contents
*/
private static final boolean areEqual (byte[] a, byte[] b) {
int aLength = a.length;
if (aLength != b.length)
return false;
for (int i = 0; i < aLength; i++)
if (a[i] != b[i])
return false;
return true;
}
/**
* Returns a string of 2 hexadecimal digits (most significant
* digit first) corresponding to the lowest 8 bits of <i>n</i>.
*/
private static final String byteToString (int n) {
char[] buf = {
_HEX_DIGITS[(n >>> 4) & 0x0F],
_HEX_DIGITS[ n & 0x0F]
};
return new String(buf);
}
/**
* Returns a string of 8 hexadecimal digits (most significant
* digit first) corresponding to the integer <i>n</i>, which is
* treated as unsigned.
*/
private static final String intToString (int n) {
char[] buf = new char[8];
for (int i = 7; i >= 0; i--) {
buf[i] = _HEX_DIGITS[n & 0x0F];
n >>>= 4;
}
return new String(buf);
}
/**
* Returns a string of hexadecimal digits from a byte array. Each
* byte is converted to 2 hex symbols.
*/
private static final String toString (byte[] ba) {
int length = ba.length;
char[] buf = new char[length * 2];
for (int i = 0, j = 0, k; i < length; ) {
k = ba[i++];
buf[j++] = _HEX_DIGITS[(k >>> 4) & 0x0F];
buf[j++] = _HEX_DIGITS[ k & 0x0F];
}
return new String(buf);
}
/**
* Returns a string of hexadecimal digits from an integer array. Each
* int is converted to 4 hex symbols.
*/
private static final String toString (int[] ia) {
int length = ia.length;
char[] buf = new char[length * 8];
for (int i = 0, j = 0, k; i < length; i++) {
k = ia[i];
buf[j++] = _HEX_DIGITS[(k >>> 28) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 24) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 20) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 16) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 12) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 8) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 4) & 0x0F];
buf[j++] = _HEX_DIGITS[ k & 0x0F];
}
return new String(buf);
}
// main(): use to generate the Intermediate Values KAT
//...........................................................................
public static void main (String[] args) {
self_test(16);
self_test(24);
self_test(32);
}
}

View File

@ -0,0 +1,64 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the TheCrypto may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.math.BigInteger;
import net.i2p.util.NativeBigInteger;
/**
* Primes for ElGamal and DSA from
* http://www.ietf.org/proceedings/03mar/I-D/draft-ietf-ipsec-ike-modp-groups-05.txt
*/
public class CryptoConstants {
public static final BigInteger dsap = new NativeBigInteger(
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"+
"a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"+
"f6c3e4548707fef8503d91dd8602e867e6d35d2235c1869ce2479c3b9d5401de04e0727f"+
"b33d6511285d4cf29538d9e3b6051f5b22cc1c93", 16);
public static final BigInteger dsaq = new NativeBigInteger(
"a5dfc28fef4ca1e286744cd8eed9d29d684046b7", 16);
public static final BigInteger dsag = new NativeBigInteger(
"c1f4d27d40093b429e962d7223824e0bbc47e7c832a39236fc683af84889581075ff9082"+
"ed32353d4374d7301cda1d23c431f4698599dda02451824ff369752593647cc3ddc197de"+
"985e43d136cdcfc6bd5409cd2f450821142a5e6f8eb1c3ab5d0484b8129fcf17bce4f7f3"+
"3321c3cb3dbb14a905e7b2b3e93be4708cbcc82", 16);
public static final BigInteger elgp = new NativeBigInteger(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"+
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+
"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
public static final BigInteger elgg = new NativeBigInteger("2");
}

View File

@ -0,0 +1,312 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.SessionKey;
import net.i2p.data.ByteArray;
import net.i2p.util.RandomSource;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.Clock;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* Generate a new session key through a diffie hellman exchange. This uses the
* constants defined in CryptoConstants, which causes the exchange to create a
* 256 bit session key.
*
* This class precalcs a set of values on its own thread, using those transparently
* when a new instance is created. By default, the minimum threshold for creating
* new values for the pool is 5, and the max pool size is 10. Whenever the pool has
* less than the minimum, it fills it up again to the max. There is a delay after
* each precalculation so that the CPU isn't hosed during startup (defaulting to 1 second).
* These three parameters are controlled by java environmental variables and
* can be adjusted via:
* -Dcrypto.dh.precalc.min=40 -Dcrypto.dh.precalc.max=100 -Dcrypto.dh.precalc.delay=60000
*
* (delay is milliseconds)
*
* To disable precalculation, set min to 0
*
* @author jrandom
*/
public class DHSessionKeyBuilder {
private final static Log _log = new Log(DHSessionKeyBuilder.class);
private static int MIN_NUM_BUILDERS = -1;
private static int MAX_NUM_BUILDERS = -1;
private static int CALC_DELAY = -1;
private static volatile List _builders = new ArrayList(50);
private static Thread _precalcThread = null;
private BigInteger _myPrivateValue;
private BigInteger _myPublicValue;
private BigInteger _peerValue;
private SessionKey _sessionKey;
private ByteArray _extraExchangedBytes; // bytes after the session key from the DH exchange
public final static String PROP_DH_PRECALC_MIN = "crypto.dh.precalc.min";
public final static String PROP_DH_PRECALC_MAX = "crypto.dh.precalc.max";
public final static String PROP_DH_PRECALC_DELAY = "crypto.dh.precalc.delay";
public final static String DEFAULT_DH_PRECALC_MIN = "5";
public final static String DEFAULT_DH_PRECALC_MAX = "10";
public final static String DEFAULT_DH_PRECALC_DELAY = "1000";
static {
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
MIN_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MIN);
MIN_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
MAX_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MAX);
MAX_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
CALC_DELAY = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_DELAY);
CALC_DELAY = val;
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("DH Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " + CALC_DELAY + ")");
_precalcThread = new Thread(new DHSessionKeyBuilderPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS));
_precalcThread.setName("DH Precalc");
_precalcThread.setDaemon(true);
_precalcThread.setPriority(Thread.MIN_PRIORITY);
_precalcThread.start();
}
/**
* Construct a new DH key builder
*
*/
public DHSessionKeyBuilder() {
this(false);
DHSessionKeyBuilder builder = null;
synchronized (_builders) {
if (_builders.size() > 0) {
builder = (DHSessionKeyBuilder)_builders.remove(0);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing a builder. # left = " + _builders.size());
} else {
if (_log.shouldLog(Log.WARN)) _log.warn("NO MORE BUILDERS! creating one now");
}
}
if (builder != null) {
_myPrivateValue = builder._myPrivateValue;
_myPublicValue = builder._myPublicValue;
_peerValue = builder._peerValue;
_sessionKey = builder._sessionKey;
_extraExchangedBytes = builder._extraExchangedBytes;
} else {
_myPrivateValue = null;
_myPublicValue = null;
_peerValue = null;
_sessionKey = null;
_myPublicValue = generateMyValue();
_extraExchangedBytes = new ByteArray();
}
}
public DHSessionKeyBuilder(boolean usePool) {
_myPrivateValue = null;
_myPublicValue = null;
_peerValue = null;
_sessionKey = null;
_extraExchangedBytes = new ByteArray();
}
private static final int getSize() { synchronized (_builders) { return _builders.size(); } }
private static final int addBuilder(DHSessionKeyBuilder builder) {
int sz = 0;
synchronized (_builders) {
_builders.add(builder);
sz = _builders.size();
}
return sz;
}
/**
* Create a new private value for the DH exchange, and return the number to
* be exchanged, leaving the actual private value accessible through getMyPrivateValue()
*
*/
public BigInteger generateMyValue() {
long start = Clock.getInstance().now();
_myPrivateValue = new NativeBigInteger(2048, RandomSource.getInstance());
BigInteger myValue = CryptoConstants.elgg.modPow(_myPrivateValue, CryptoConstants.elgp);
long end = Clock.getInstance().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took more than a second (" + diff + "ms) to generate local DH value");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Took " + diff + "ms to generate local DH value");
}
return myValue;
}
/**
* Retrieve the private value used by the local participant in the DH exchange
*/
public BigInteger getMyPrivateValue() { return _myPrivateValue; }
/**
* Retrieve the public value used by the local participant in the DH exchange,
* generating it if necessary
*/
public BigInteger getMyPublicValue() {
if (_myPublicValue == null)
_myPublicValue = generateMyValue();
return _myPublicValue;
}
/**
* Specify the value given by the peer for use in the session key negotiation
*
*/
public void setPeerPublicValue(BigInteger peerVal) { _peerValue = peerVal; }
public BigInteger getPeerPublicValue() { return _peerValue; }
/**
* Retrieve the session key, calculating it if necessary (and if possible).
*
* @return session key exchanged, or null if the exchange is not complete
*/
public SessionKey getSessionKey() {
if (_sessionKey != null) return _sessionKey;
if (_peerValue != null) {
if (_myPrivateValue == null) generateMyValue();
_sessionKey = calculateSessionKey(_myPrivateValue, _peerValue);
} else {
System.err.println("Not ready yet.. privateValue and peerValue must be set (" + (_myPrivateValue != null ? "set":"null") + "," + (_peerValue != null ? "set":"null") + ")");
}
return _sessionKey;
}
/**
* Retrieve the extra bytes beyond the session key resulting from the DH exchange.
* If there aren't enough bytes (with all of them being consumed by the 32 byte key),
* the SHA256 of the key itself is used.
*
*/
public ByteArray getExtraBytes() { return _extraExchangedBytes; }
/**
* Calculate a session key based on the private value and the public peer value
*
*/
private final SessionKey calculateSessionKey(BigInteger myPrivateValue, BigInteger publicPeerValue) {
long start = Clock.getInstance().now();
SessionKey key = new SessionKey();
BigInteger exchangedKey = publicPeerValue.modPow(myPrivateValue, CryptoConstants.elgp);
byte buf[] = exchangedKey.toByteArray();
byte val[] = new byte[32];
if (buf.length < val.length) {
System.arraycopy(buf, 0, val, 0, buf.length);
byte remaining[] = SHA256Generator.getInstance().calculateHash(val).getData();
_extraExchangedBytes.setData(remaining);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Storing " + remaining.length + " bytes from the DH exchange by SHA256 the session key");
} else { // (buf.length >= val.length)
System.arraycopy(buf, 0, val, 0, val.length);
byte remaining[] = new byte[buf.length - val.length];
System.arraycopy(buf, val.length, remaining, 0, remaining.length);
_extraExchangedBytes.setData(remaining);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Storing " + remaining.length + " bytes from the end of the DH exchange");
}
key.setData(val);
long end = Clock.getInstance().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Generating session key took too long ("+ diff +" ms");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Generating session key "+ diff +" ms");
}
return key;
}
public static void main(String args[]) {
RandomSource.getInstance().nextBoolean(); // warm it up
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
_log.debug("\n\n\n\nBegin test\n");
long negTime = 0;
for (int i = 0; i < 5; i++) {
long startNeg = Clock.getInstance().now();
DHSessionKeyBuilder builder1 = new DHSessionKeyBuilder();
DHSessionKeyBuilder builder2 = new DHSessionKeyBuilder();
BigInteger pub1 = builder1.getMyPublicValue();
builder2.setPeerPublicValue(pub1);
BigInteger pub2 = builder2.getMyPublicValue();
builder1.setPeerPublicValue(pub2);
SessionKey key1 = builder1.getSessionKey();
SessionKey key2 = builder2.getSessionKey();
long endNeg = Clock.getInstance().now();
negTime += endNeg - startNeg;
if (!key1.equals(key2))
_log.error("**ERROR: Keys do not match");
else
_log.debug("**Success: Keys match");
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
String origVal = "1234567890123456"; // 16 bytes max using AESEngine
byte enc[] = AESEngine.getInstance().encrypt(origVal.getBytes(), key1, iv);
byte dec[] = AESEngine.getInstance().decrypt(enc, key2, iv);
String tranVal = new String(dec);
if (origVal.equals(tranVal))
_log.debug("**Success: D(E(val)) == val");
else
_log.error("**ERROR: D(E(val)) != val [val=(" + tranVal + "), origVal=(" + origVal + ")");
}
_log.debug("Negotiation time for 5 runs: " + negTime + " @ " + negTime/5l + "ms each");
try { Thread.sleep(2000); } catch (InterruptedException ie) {}
}
private static class DHSessionKeyBuilderPrecalcRunner implements Runnable {
private int _minSize;
private int _maxSize;
private DHSessionKeyBuilderPrecalcRunner(int minSize, int maxSize) {
_minSize = minSize;
_maxSize = maxSize;
}
public void run() {
while (true) {
int curSize = 0;
long start = Clock.getInstance().now();
int startSize = getSize();
curSize = startSize;
while (curSize < _minSize) {
while (curSize < _maxSize) {
curSize = addBuilder(precalc(curSize));
// for some relief...
try { Thread.sleep(CALC_DELAY); } catch (InterruptedException ie) {}
}
}
long end = Clock.getInstance().now();
int numCalc = curSize - startSize;
if (numCalc > 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalced " + numCalc + " to " + curSize + " in " + (end-start-CALC_DELAY*numCalc) + "ms (not counting " + (CALC_DELAY*numCalc) + "ms relief). now sleeping");
}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
}
private DHSessionKeyBuilder precalc(int i) {
DHSessionKeyBuilder builder = new DHSessionKeyBuilder(false);
builder.getMyPublicValue();
//_log.debug("Precalc " + i + " complete");
return builder;
}
}
}

View File

@ -0,0 +1,281 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the TheCrypto may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.Hash;
import net.i2p.crypto.CryptoConstants;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import java.math.BigInteger;
public class DSAEngine {
private final static Log _log = new Log(DSAEngine.class);
private static DSAEngine _instance = new DSAEngine();
public static DSAEngine getInstance() { return _instance; }
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
long start = Clock.getInstance().now();
byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20];
byte sbytes[] = new byte[20];
for (int x = 0; x < 40; x++) {
if (x < 20) {
rbytes[x] = sigbytes[x];
} else {
sbytes[x-20] = sigbytes[x];
}
}
BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
BigInteger u1 = ((new NativeBigInteger(1, calculateHash(signedData).getData())).multiply(w)).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
BigInteger v = ((CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap)).multiply(y.modPow(u2, CryptoConstants.dsap))).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
boolean ok = v.compareTo(r) == 0;
long diff = Clock.getInstance().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to verify the signature ("+ diff + "ms)");
}
return ok;
}
public Signature sign(byte data[], SigningPrivateKey signingKey) {
if ( (signingKey == null) || (data == null) || (data.length <= 0) ) return null;
long start = Clock.getInstance().now();
Signature sig = new Signature();
BigInteger k;
do {
k = new BigInteger(160, RandomSource.getInstance());
} while (k.compareTo(CryptoConstants.dsaq) != 1);
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
Hash h = calculateHash(data);
if (h == null) return null;
BigInteger M = new NativeBigInteger(1, h.getData());
BigInteger x = new NativeBigInteger(1, signingKey.getData());
BigInteger s = (kinv.multiply(M.add(x.multiply(r)))).mod(CryptoConstants.dsaq);
byte[] rbytes = r.toByteArray();
byte[] sbytes = s.toByteArray();
byte[] out = new byte[40];
if (rbytes.length == 20) {
for (int i = 0; i < 20; i++) {
out[i] = rbytes[i];
}
} else if (rbytes.length == 21) {
for (int i = 0; i < 20; i++) {
out[i] = rbytes[i+1];
}
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]");
for (int i = 0; i < rbytes.length; i++)
out[i+20-rbytes.length] = rbytes[i];
}
if (sbytes.length == 20) {
for (int i = 0; i < 20; i++) {
out[i+20] = sbytes[i];
}
} else if (sbytes.length == 21) {
for (int i = 0; i < 20; i++) {
out[i+20] = sbytes[i+1];
}
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]");
for (int i = 0; i< sbytes.length; i++)
out[i+20+20-sbytes.length] = sbytes[i];
}
sig.setData(out);
long diff = Clock.getInstance().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to sign (" + diff + "ms)");
}
return sig;
}
private int[] H0 = {
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
};
private Hash calculateHash(byte[]source) {
long length = source.length * 8;
int k = 448 - (int) ((length + 1) % 512);
if (k < 0) {
k += 512;
}
int padbytes = k / 8;
int wordlength = (int) (source.length / 4 + padbytes / 4 + 3);
int[] M0 = new int[wordlength];
int wordcount = 0;
int x = 0;
for (x = 0; x < (source.length / 4) * 4; x += 4) {
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= source[x + 3] << 24 >>> 24 << 0;
wordcount++;
}
switch (source.length - (wordcount + 1) * 4 + 4) {
case 0:
M0[wordcount] |= 0x80000000;
break;
case 1:
M0[wordcount] = source[x] << 24 >>> 24 << 24;
M0[wordcount] |= 0x00800000;
break;
case 2:
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= 0x00008000;
break;
case 3:
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= 0x00000080;
break;
}
M0[wordlength - 2] = (int) (length >>> 32);
M0[wordlength - 1] = (int) (length);
int[] H = new int[5];
for (x = 0; x < 5; x++) {
H[x] = H0[x];
}
int blocks = M0.length / 16;
for (int bl = 0; bl < blocks; bl++) {
int a = H[0];
int b = H[1];
int c = H[2];
int d = H[3];
int e = H[4];
int[] W = new int[80];
for (x = 0; x < 80; x++) {
if (x < 16) {
W[x] = M0[bl * 16 + x];
} else {
W[x] = ROTL(1, W[x - 3] ^ W[x - 8] ^ W[x - 14] ^ W[x - 16]);
}
}
for (x = 0; x < 80; x++) {
int T = add(ROTL(5, a), add(f(x, b, c, d), add(e, add(k(x), W[x]))));
e = d;
d = c;
c = ROTL(30, b);
b = a;
a = T;
}
H[0] = add(a, H[0]);
H[1] = add(b, H[1]);
H[2] = add(c, H[2]);
H[3] = add(d, H[3]);
H[4] = add(e, H[4]);
}
byte[] hashbytes = new byte[20];
for (x = 0; x < 5; x++) {
hashbytes[ x * 4 ] = (byte) (H[x] << 0 >>> 24);
hashbytes[x * 4 + 1] = (byte) (H[x] << 8 >>> 24);
hashbytes[x * 4 + 2] = (byte) (H[x] << 16 >>> 24);
hashbytes[x * 4 + 3] = (byte) (H[x] << 24 >>> 24);
}
Hash hash = new Hash();
hash.setData(hashbytes);
return hash;
}
private int k(int t) {
if (t > -1 && t < 20) {
return 0x5a827999;
} else if (t > 19 && t < 40) {
return 0x6ed9eba1;
} else if (t > 39 && t < 60) {
return 0x8f1bbcdc;
} else if (t > 59 && t < 80) {
return 0xca62c1d6;
}
return 0x00000000;
}
private int f(int t, int x, int y, int z) {
if (t > -1 && t < 20) {
return Ch(x, y, z);
} else if (t > 19 && t < 40) {
return Parity(x, y, z);
} else if (t > 39 && t < 60) {
return Maj(x, y, z);
} else if (t > 59 && t < 80) {
return Parity(x, y, z);
}
return 0x00000000;
}
private int Ch(int x, int y, int z) {
return (x & y) ^ (~x & z);
}
private int Parity(int x, int y, int z) {
return x ^ y ^ z;
}
private int Maj(int x, int y, int z) {
return (x & y) ^ (x & z) ^ (y & z);
}
private int ROTL(int n, int x) {
return (x << n) | (x >>> 32 - n);
}
private int add(int x, int y) {
return x + y;
}
}

View File

@ -0,0 +1,92 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.util.Log;
/**
* Fake ElG E and D, useful for when performance isn't being tested
*
* @author jrandom
* @license GPL
*/
public class DummyElGamalEngine extends ElGamalEngine {
private final static Log _log = new Log(DummyElGamalEngine.class);
public DummyElGamalEngine() {
_log.log(Log.CRIT, "Dummy ElGamal engine in use! NO DATA SECURITY. Danger Will Robinson, Danger!", new Exception("I really hope you know what you're doing"));
}
/** encrypt the data to the public key
* @return encrypted data
* @param publicKey public key encrypt to
* @param data data to encrypt
*/
public byte[] encrypt(byte data[], PublicKey publicKey) {
if ( (data == null) || (data.length >= 223) ) throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
baos.write(0xFF);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
hash.writeBytes(baos);
baos.write(data);
baos.flush();
} catch (Exception e) {
_log.error("Internal error writing to buffer", e);
return null;
}
byte d2[] = baos.toByteArray();
byte[] out = new byte[514];
System.arraycopy(d2, 0, out, (d2.length < 257 ? 257 - d2.length : 0), (d2.length > 257 ? 257 : d2.length));
return out;
}
/** Decrypt the data
* @param encrypted encrypted data
* @param privateKey private key to decrypt with
* @return unencrypted data
*/
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
if ( (encrypted == null) || (encrypted.length > 514) ) throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
byte val[] = new byte[257];
System.arraycopy(encrypted, 0, val, 0, val.length);
int i = 0;
for (i = 0; i < val.length; i++)
if (val[i] != (byte)0x00)
break;
ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
Hash hash = new Hash();
byte rv[] = null;
try {
bais.read(); // skip first byte
hash.readBytes(bais);
rv = new byte[val.length - i - 1 - 32];
bais.read(rv);
} catch (Exception e) {
_log.error("Internal error reading value", e);
return null;
}
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
if (calcHash.equals(hash)) {
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
return rv;
} else {
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv), new Exception("Doesn't match"));
return null;
}
}
}

View File

@ -0,0 +1,520 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.util.Clock;
import net.i2p.stat.StatManager;
/**
* Handles the actual ElGamal+AES encryption and decryption scenarios using the
* supplied keys and data.
*/
public class ElGamalAESEngine {
private final static Log _log = new Log(ElGamalAESEngine.class);
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
static {
StatManager.getInstance().createFrequencyStat("crypto.elGamalAES.encryptNewSession",
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?", "Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l } );
StatManager.getInstance().createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?", "Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l } );
StatManager.getInstance().createFrequencyStat("crypto.elGamalAES.decryptNewSession",
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?", "Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l } );
StatManager.getInstance().createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?", "Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l } );
StatManager.getInstance().createFrequencyStat("crypto.elGamalAES.decryptFail",
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption", new long[] { 60*60*1000l, 24*60*60*1000l } );
}
/**
* Decrypt the message using the given private key. This works according to the
* ElGamal+AES algorithm in the data structure spec.
*
*/
public static byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Null data being decrypted?");
return null;
} else if (data.length < MIN_ENCRYPTED_SIZE) {
if (_log.shouldLog(Log.WARN)) _log.warn("Data is less than the minimum size (" + data.length +" < " + MIN_ENCRYPTED_SIZE + ")");
return null;
}
byte tag[] = new byte[32];
System.arraycopy(data, 0, tag, 0, tag.length);
SessionTag st = new SessionTag(tag);
SessionKey key = SessionKeyManager.getInstance().consumeTag(st);
SessionKey foundKey = new SessionKey();
foundKey.setData(null);
SessionKey usedKey = new SessionKey();
Set foundTags = new HashSet();
byte decrypted[] = null;
if (key != null) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is known for tag " + st);
usedKey.setData(key.getData());
decrypted = decryptExistingSession(data, key, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null)
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptExistingSession");
else
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is NOT known for tag " + st);
decrypted = decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null)
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptNewSession");
else
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
}
if ( (key == null) && (decrypted == null) ) {
//_log.debug("Unable to decrypt the data starting with tag [" + st + "] - did the tag expire recently?", new Exception("Decrypt failure"));
}
if (foundTags.size() > 0) {
if (foundKey.getData() != null) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Found key: " + foundKey);
SessionKeyManager.getInstance().tagsReceived(foundKey, foundTags);
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Used key: " + usedKey);
SessionKeyManager.getInstance().tagsReceived(usedKey, foundTags);
}
}
return decrypted;
}
/**
* scenario 1:
* Begin with 222 bytes, ElG encrypted, containing:
* - 32 byte SessionKey
* - 32 byte pre-IV for the AES
* - 158 bytes of random padding
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV, using
* the decryptAESBlock method & structure.
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*
* @return null if decryption fails
*/
static byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session");
return null;
} else if (data.length < 514) {
if (_log.shouldLog(Log.WARN)) _log.warn("Data length is too small ("+ data.length + ")");
return null;
}
byte elgEncr[] = new byte[514];
if (data.length > 514) {
System.arraycopy(data, 0, elgEncr, 0, 514);
} else {
System.arraycopy(data, 0, elgEncr, 514-data.length, data.length);
}
byte elgDecr[] = ElGamalEngine.getInstance().decrypt(elgEncr, targetPrivateKey);
if (elgDecr == null)
return null;
ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr);
byte preIV[] = null;
try {
usedKey.readBytes(bais);
preIV = new byte[32];
int read = bais.read(preIV);
if (read != preIV.length) {
// hmm, this can't really happen...
throw new DataFormatException("Somehow ElGamal broke and 256 bytes is less than 32 bytes...");
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error decrypting the new session", ioe);
return null;
}
// ignore the next 192 bytes
byte aesEncr[] = new byte[data.length - 514];
System.arraycopy(data, 514, aesEncr, 0, aesEncr.length);
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesDecr[] = decryptAESBlock(aesEncr, usedKey, iv, null, foundTags, foundKey);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(), new Exception("Decrypted by"));
return aesDecr;
}
/**
* scenario 2:
* The data begins with 32 byte session tag, which also serves as the preIV.
* Then decrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
* If anything doesn't match up in decryption, it falls back to decryptNewSession
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*
*/
static byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
byte preIV[] = new byte[32];
System.arraycopy(data, 0, preIV, 0, preIV.length);
byte encr[] = new byte[data.length-32];
System.arraycopy(data, 32, encr, 0, encr.length);
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
usedKey.setData(key.getData());
//_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
byte decrypted[] = decryptAESBlock(encr, key, iv, preIV, foundTags, foundKey);
if (decrypted == null) {
// it begins with a valid session tag, but thats just a coincidence.
if (_log.shouldLog(Log.DEBUG)) _log.debug("Decrypt with a non session tag, but tags read: " + foundTags.size());
return decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
} else {
// existing session decrypted successfully!
if (_log.shouldLog(Log.DEBUG)) _log.debug("Decrypt with an EXISTING session tag successfull, # tags read: " + foundTags.size(), new Exception("Decrypted by"));
return decrypted;
}
}
/**
* Decrypt the AES data with the session key and IV. The result should be:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
* If anything doesn't match up in decryption, return null. Otherwise, return
* the decrypted data and update the session as necessary. If the sentTag is not null,
* consume it, but if it is null, record the keys, etc as part of a new session.
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*/
static byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));
//_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
byte decrypted[] = AESEngine.getInstance().decrypt(encrypted, key, iv);
Hash h = SHA256Generator.getInstance().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try {
SessionKey newKey = null;
Hash readHash = null;
List tags = new ArrayList();
ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
long numTags = DataHelper.readLong(bais, 2);
//_log.debug("# tags: " + numTags);
if ( (numTags < 0) || (numTags > 65535) )
throw new Exception("Invalid number of session tags");
for (int i = 0; i < numTags; i++) {
byte tag[] = new byte[32];
int read = bais.read(tag);
if (read != 32)
throw new Exception("Invalid session tag - # tags: " + numTags + " curTag #: " + i + " read: " + read);
tags.add(new SessionTag(tag));
}
long len = DataHelper.readLong(bais, 4);
//_log.debug("len: " + len);
if ( (len < 0) || (len > encrypted.length) )
throw new Exception("Invalid size of payload");
byte hashval[] = new byte[32];
int read = bais.read(hashval);
if (read != hashval.length)
throw new Exception("Invalid size of hash");
readHash = new Hash();
readHash.setData(hashval);
byte flag = (byte)bais.read();
if (flag == 0x01) {
byte rekeyVal[] = new byte[32];
read = bais.read(rekeyVal);
if (read != rekeyVal.length)
throw new Exception("Invalid size of the rekeyed session key");
newKey = new SessionKey();
newKey.setData(rekeyVal);
}
byte unencrData[] = new byte[(int)len];
read = bais.read(unencrData);
if (read != unencrData.length)
throw new Exception("Invalid size of the data read");
Hash calcHash = SHA256Generator.getInstance().calculateHash(unencrData);
if (calcHash.equals(readHash)) {
// everything matches. w00t.
foundTags.addAll(tags);
if (newKey != null)
foundKey.setData(newKey.getData());
return unencrData;
} else {
throw new Exception("Hash does not match");
}
} catch (Exception e) {
if (_log.shouldLog(Log.WARN)) _log.warn("Unable to decrypt AES block", e);
return null;
}
}
/**
* Encrypt the unencrypted data to the target. The total size returned will be
* no less than the paddedSize parameter, but may be more. This method uses the
* ElGamal+AES algorithm in the data structure spec.
*
* @param target public key to which the data should be encrypted.
* @param key session key to use during encryption
* @param tagsForDelivery session tags to be associated with the key (or newKey if specified), or null
* @param currentTag sessionTag to use, or null if it should use ElG
* @param newKey key to be delivered to the target, with which the tagsForDelivery should be associated
* @param paddedSize minimum size in bytes of the body after padding it (if less than the
* body's real size, no bytes are appended but the body is not truncated)
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) {
if (currentTag == null) {
if (_log.shouldLog(Log.INFO)) _log.info("Current tag is null, encrypting as new session", new Exception("encrypt new"));
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptNewSession");
return encryptNewSession(data, target, key, tagsForDelivery, newKey, paddedSize);
} else {
if (_log.shouldLog(Log.INFO)) _log.info("Current tag is NOT null, encrypting as existing session", new Exception("encrypt existing"));
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptExistingSession");
return encryptExistingSession(data, target, key, tagsForDelivery, currentTag, newKey, paddedSize);
}
}
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, currentTag, null, paddedSize);
}
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, null, null, paddedSize);
}
/**
* Encrypt the data to the target using the given key delivering no tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
return encrypt(data, target, key, null, null, null, paddedSize);
}
/**
* scenario 1:
* Begin with 222 bytes, ElG encrypted, containing:
* - 32 byte SessionKey
* - 32 byte pre-IV for the AES
* - 158 bytes of random padding
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
static byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to a NEW session");
try {
ByteArrayOutputStream elgSrc = new ByteArrayOutputStream(64);
key.writeBytes(elgSrc);
byte preIV[] = new byte[32];
RandomSource.getInstance().nextBytes(preIV);
elgSrc.write(preIV);
byte rnd[] = new byte[158];
RandomSource.getInstance().nextBytes(rnd);
elgSrc.write(rnd);
elgSrc.flush();
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = Clock.getInstance().now();
byte elgEncr[] = ElGamalEngine.getInstance().encrypt(elgSrc.toByteArray(), target);
long after = Clock.getInstance().now();
if (_log.shouldLog(Log.INFO)) _log.info("elgEngine.encrypt of the session key took " + (after-before) + "ms");
if (elgEncr.length < 514) {
byte elg[] = new byte[514];
int diff = elg.length - elgEncr.length;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff);
System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length);
elgEncr = elg;
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = Clock.getInstance().now();
if (_log.shouldLog(Log.DEBUG)) _log.debug("after the elgEngine.encrypt took a total of " + (finish-after) +"ms");
return rv;
} catch (IOException ioe) {
_log.error("Error encrypting the new session", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing out the bytes for the new session", dfe);
return null;
}
}
/**
* scenario 2:
* Begin with 32 byte session tag, which also serves as the preIV.
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
static byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to an EXISTING session");
byte rawTag[] = currentTag.getData();
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = SHA256Generator.getInstance().calculateHash(rawTag);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
byte rv[] = new byte[rawTag.length + aesEncr.length];
System.arraycopy(rawTag, 0, rv, 0, rawTag.length);
System.arraycopy(aesEncr, 0, rv, rawTag.length, aesEncr.length);
return rv;
}
private final static Set EMPTY_SET = new HashSet();
/**
* For both scenarios, this method encrypts the AES area using the given key, iv
* and making sure the resulting data is at least as long as the paddedSize and
* also mod 16 bytes. The contents of the encrypted data is:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
final static byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, long paddedSize) {
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
//_log.debug("Encrypting AES");
try {
ByteArrayOutputStream aesSrc = new ByteArrayOutputStream((int)paddedSize);
if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET;
DataHelper.writeLong(aesSrc, 2, tagsForDelivery.size());
for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext(); ) {
SessionTag tag = (SessionTag)iter.next();
aesSrc.write(tag.getData());
}
//_log.debug("# tags created, registered, and written: " + tags.size());
DataHelper.writeLong(aesSrc, 4, data.length);
//_log.debug("data length: " + data.length);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
hash.writeBytes(aesSrc);
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
byte flag = 0x00; // don't rekey
aesSrc.write(flag);
//_log.debug("flag written");
} else {
byte flag = 0x01; // rekey
aesSrc.write(flag);
aesSrc.write(newKey.getData());
}
aesSrc.write(data);
int len = aesSrc.toByteArray().length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(len, paddedSize);
//_log.debug("padding length: " + padding.length);
aesSrc.write(padding);
byte aesUnencr[] = aesSrc.toByteArray();
Hash h = SHA256Generator.getInstance().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
byte aesEncr[] = AESEngine.getInstance().encrypt(aesUnencr, key, iv);
//_log.debug("Encrypted length: " + aesEncr.length);
return aesEncr;
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error encrypting AES chunk", ioe);
return null;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error formatting the bytes to write the AES chunk", dfe);
return null;
}
}
/**
* Return random bytes for padding the data to a mod 16 size so that it is
* at least minPaddedSize
*
*/
final static byte[] getPadding(int curSize, long minPaddedSize) {
int diff = 0;
if (curSize < minPaddedSize) {
diff = (int)minPaddedSize - curSize;
}
int numPadding = diff;
if (((curSize + diff) % 16) != 0)
numPadding += (16-((curSize + diff) % 16));
byte rv[] = new byte[numPadding];
RandomSource.getInstance().nextBytes(rv);
return rv;
}
}

View File

@ -0,0 +1,258 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the TheCrypto may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.stat.StatManager;
/**
* Wrapper for ElGamal encryption/signature schemes.
*
* Does all of Elgamal now for data sizes of 223 bytes and less. The data to be
* encrypted is first prepended with a random nonzero byte, then the 32 bytes
* making up the SHA256 of the data, then the data itself. The random byte and
* the SHA256 hash is stripped on decrypt so the original data is returned.
*
* @author thecrypto, jrandom
*/
public class ElGamalEngine {
private final static Log _log = new Log(ElGamalEngine.class);
private static ElGamalEngine _engine;
static {
if ("off".equals(System.getProperty("i2p.encryption", "on")))
_engine = new DummyElGamalEngine();
else
_engine = new ElGamalEngine();
StatManager.getInstance().createRateStat("crypto.elGamal.encrypt",
"how long does it take to do a full ElGamal encryption", "Encryption", new long[] { 60*1000, 60*60*1000, 24*60*60*1000 } );
StatManager.getInstance().createRateStat("crypto.elGamal.decrypt",
"how long does it take to do a full ElGamal decryption", "Encryption", new long[] { 60*1000, 60*60*1000, 24*60*60*1000 } );
}
public static ElGamalEngine getInstance() { return _engine; }
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02 } );
private BigInteger[] getNextYK() { return YKGenerator.getNextYK(); }
/** encrypt the data to the public key
* @return encrypted data
* @param publicKey public key encrypt to
* @param data data to encrypt
*/
public byte[] encrypt(byte data[], PublicKey publicKey) {
if ( (data == null) || (data.length >= 223) ) throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
long start = Clock.getInstance().now();
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
baos.write(0xFF);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
hash.writeBytes(baos);
baos.write(data);
baos.flush();
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR)) _log.error("Internal error writing to buffer", e);
return null;
}
byte d2[] = baos.toByteArray();
long t0 = Clock.getInstance().now();
BigInteger m = new NativeBigInteger(1, d2);
long t1 = Clock.getInstance().now();
if (m.compareTo(CryptoConstants.elgp) >= 0)
throw new IllegalArgumentException("ARGH. Data cannot be larger than the ElGamal prime. FIXME");
long t2 = Clock.getInstance().now();
BigInteger aalpha = new NativeBigInteger(1, publicKey.getData());
long t3 = Clock.getInstance().now();
BigInteger yk[] = getNextYK();
BigInteger k = yk[1];
BigInteger y = yk[0];
long t7 = Clock.getInstance().now();
BigInteger d = aalpha.modPow(k, CryptoConstants.elgp);
long t8 = Clock.getInstance().now();
d = d.multiply(m);
long t9 = Clock.getInstance().now();
d = d.mod(CryptoConstants.elgp);
long t10 = Clock.getInstance().now();
byte[] ybytes = y.toByteArray();
byte[] dbytes = d.toByteArray();
byte[] out = new byte[514];
System.arraycopy(ybytes, 0, out, (ybytes.length < 257 ? 257 - ybytes.length : 0), (ybytes.length > 257 ? 257 : ybytes.length));
System.arraycopy(dbytes, 0, out, (dbytes.length < 257 ? 514 - dbytes.length : 257), (dbytes.length > 257 ? 257 : dbytes.length));
StringBuffer buf = new StringBuffer(1024);
buf.append("Timing\n");
buf.append("0-1: ").append(t1-t0).append('\n');
buf.append("1-2: ").append(t2-t1).append('\n');
buf.append("2-3: ").append(t3-t2).append('\n');
//buf.append("3-4: ").append(t4-t3).append('\n');
//buf.append("4-5: ").append(t5-t4).append('\n');
//buf.append("5-6: ").append(t6-t5).append('\n');
//buf.append("6-7: ").append(t7-t6).append('\n');
buf.append("7-8: ").append(t8-t7).append('\n');
buf.append("8-9: ").append(t9-t8).append('\n');
buf.append("9-10: ").append(t10-t9).append('\n');
//_log.debug(buf.toString());
long end = Clock.getInstance().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to encrypt ElGamal block (" + diff + "ms)");
}
StatManager.getInstance().addRateData("crypto.elGamal.encrypt", diff, diff);
return out;
}
/** Decrypt the data
* @param encrypted encrypted data
* @param privateKey private key to decrypt with
* @return unencrypted data
*/
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
if ( (encrypted == null) || (encrypted.length > 514) ) throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
long start = Clock.getInstance().now();
byte[] ybytes = new byte[257];
byte[] dbytes = new byte[257];
System.arraycopy(encrypted, 0, ybytes, 0, 257);
System.arraycopy(encrypted, 257, dbytes, 0, 257);
BigInteger y = new NativeBigInteger(1, ybytes);
BigInteger d = new NativeBigInteger(1, dbytes);
BigInteger a = new NativeBigInteger(1, privateKey.getData());
BigInteger y1p = CryptoConstants.elgp.subtract(BigInteger.ONE).subtract(a);
BigInteger ya = y.modPow(y1p, CryptoConstants.elgp);
BigInteger m = ya.multiply(d); m = m.mod(CryptoConstants.elgp);
byte val[] = m.toByteArray();
int i = 0;
for (i = 0; i < val.length; i++)
if (val[i] != (byte)0x00)
break;
ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
Hash hash = new Hash();
byte rv[] = null;
try {
bais.read(); // skip first byte
hash.readBytes(bais);
rv = new byte[val.length - i - 1 - 32];
bais.read(rv);
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR)) _log.error("Internal error reading value", e);
return null;
}
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
boolean ok = calcHash.equals(hash);
long end = Clock.getInstance().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to decrypt and verify ElGamal block (" + diff + "ms)");
}
StatManager.getInstance().addRateData("crypto.elGamal.decrypt", diff, diff);
if (ok) {
//_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
return rv;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + Base64.encode(rv), new Exception("Doesn't match"));
return null;
}
}
public static void main(String args[]) {
long eTime = 0;
long dTime = 0;
long gTime = 0;
int numRuns = 100;
if (args.length > 0)
try {
numRuns = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
RandomSource.getInstance().nextBoolean();
System.out.println("Running " + numRuns + " times");
for (int i = 0; i < numRuns; i++) {
long startG = Clock.getInstance().now();
Object pair[] = KeyGenerator.getInstance().generatePKIKeypair();
long endG = Clock.getInstance().now();
PublicKey pubkey = (PublicKey)pair[0];
PrivateKey privkey = (PrivateKey)pair[1];
byte buf[] = new byte[128];
RandomSource.getInstance().nextBytes(buf);
long startE = Clock.getInstance().now();
byte encr[] = ElGamalEngine.getInstance().encrypt(buf, pubkey);
long endE = Clock.getInstance().now();
byte decr[] = ElGamalEngine.getInstance().decrypt(encr, privkey);
long endD = Clock.getInstance().now();
eTime += endE - startE;
dTime += endD - endE;
gTime += endG - startG;
if (!DataHelper.eq(decr, buf)) {
System.out.println("PublicKey : " + DataHelper.toString(pubkey.getData(), pubkey.getData().length));
System.out.println("PrivateKey : " + DataHelper.toString(privkey.getData(), privkey.getData().length));
System.out.println("orig : " + DataHelper.toString(buf, buf.length));
System.out.println("d(e(orig) : " + DataHelper.toString(decr, decr.length));
System.out.println("orig.len : " + buf.length);
System.out.println("d(e(orig).len : " + decr.length);
System.out.println("Not equal!");
System.exit(0);
} else {
System.out.println("*Run " +i+" is successful, with encr.length = " + encr.length + " [E: " + (endE-startE) + " D: " + (endD-endE) + " G: " + (endG - startG) + "]\n");
}
}
System.out.println("\n\nAll "+numRuns+" tests successful, average encryption time: " + (eTime/numRuns) + " average decryption time: " + (dTime / numRuns) + " average key generation time: " + (gTime / numRuns));
}
}

View File

@ -0,0 +1,33 @@
package net.i2p.crypto;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.data.DataHelper;
/**
* Calculate the HMAC-SHA256 of a key+message. Currently FAKE - returns a stupid
* kludgy hash: H(H(key) XOR H(data)). Fix me!
*
*/
public abstract class HMACSHA256Generator {
private static HMACSHA256Generator _generator = new DummyHMACSHA256Generator();
public static HMACSHA256Generator getInstance() { return _generator; }
public abstract Hash calculate(SessionKey key, byte data[]);
}
/**
* jrandom smells.
*
*/
class DummyHMACSHA256Generator extends HMACSHA256Generator {
public Hash calculate(SessionKey key, byte data[]) {
if ( (key == null) || (key.getData() == null) || (data == null) )
throw new NullPointerException("Null arguments for HMAC");
Hash hkey = SHA256Generator.getInstance().calculateHash(key.getData());
Hash hdata = SHA256Generator.getInstance().calculateHash(data);
return SHA256Generator.getInstance().calculateHash(DataHelper.xor(hkey.getData(), hdata.getData()));
}
}

View File

@ -0,0 +1,158 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import net.i2p.data.DataHelper;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
/** Define a way of generating asymetrical key pairs as well as symetrical keys
* @author jrandom
*/
public class KeyGenerator {
private final static Log _log = new Log(KeyGenerator.class);
private static final RandomSource _random = RandomSource.getInstance();
private static KeyGenerator _generator = new KeyGenerator();
public static KeyGenerator getInstance() { return _generator; }
/** Generate a private 256 bit session key
* @return session key
*/
public SessionKey generateSessionKey() {
// 256bit random # as a session key
SessionKey key = new SessionKey();
byte data[] = new byte[SessionKey.KEYSIZE_BYTES];
_random.nextBytes(data);
key.setData(data);
return key;
}
/** Generate a pair of keys, where index 0 is a PublicKey, and
* index 1 is a PrivateKey
* @return pair of keys
*/
public Object[] generatePKIKeypair() {
BigInteger a = new NativeBigInteger(2048, _random);
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
Object[] keys = new Object[2];
keys[0] = new PublicKey();
keys[1] = new PrivateKey();
byte[] k0 = aalpha.toByteArray();
byte[] k1 = a.toByteArray();
// bigInteger.toByteArray returns SIGNED integers, but since they'return positive,
// signed two's complement is the same as unsigned
((PublicKey)keys[0]).setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES));
((PrivateKey)keys[1]).setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES));
return keys;
}
/** Generate a pair of DSA keys, where index 0 is a SigningPublicKey, and
* index 1 is a SigningPrivateKey
* @return pair of keys
*/
public Object[] generateSigningKeypair() {
Object[] keys = new Object[2];
BigInteger x = null;
// make sure the random key is less than the DSA q
do {
x = new NativeBigInteger(160, _random);
} while (x.compareTo(CryptoConstants.dsaq) >= 0);
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
keys[0] = new SigningPublicKey();
keys[1] = new SigningPrivateKey();
byte k0[] = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);
byte k1[] = padBuffer(x.toByteArray(), SigningPrivateKey.KEYSIZE_BYTES);
((SigningPublicKey)keys[0]).setData(k0);
((SigningPrivateKey)keys[1]).setData(k1);
return keys;
}
/**
* Pad the buffer w/ leading 0s or trim off leading bits so the result is the
* given length.
*/
private final static byte[] padBuffer(byte src[], int length) {
byte buf[] = new byte[length];
if (src.length > buf.length) // extra bits, chop leading bits
System.arraycopy(src, src.length - buf.length, buf, 0, buf.length);
else if (src.length < buf.length) // short bits, padd w/ 0s
System.arraycopy(src, 0, buf, buf.length - src.length, src.length);
else // eq
System.arraycopy(src, 0, buf, 0, buf.length);
return buf;
}
public static void main(String args[]) {
Log log = new Log("keygenTest");
RandomSource.getInstance().nextBoolean();
byte src[] = new byte[200];
RandomSource.getInstance().nextBytes(src);
long time = 0;
for (int i = 0; i < 10; i++) {
long start = Clock.getInstance().now();
Object keys[] = KeyGenerator.getInstance().generatePKIKeypair();
long end = Clock.getInstance().now();
byte ctext[] = ElGamalEngine.getInstance().encrypt(src, (PublicKey)keys[0]);
byte ptext[] = ElGamalEngine.getInstance().decrypt(ctext, (PrivateKey)keys[1]);
time += end - start;
if (DataHelper.eq(ptext, src))
log.debug("D(E(data)) == data");
else
log.error("D(E(data)) != data!!!!!!");
}
log.info("Keygen 10 times: " + time + "ms");
Object obj[] = KeyGenerator.getInstance().generateSigningKeypair();
SigningPublicKey fake = (SigningPublicKey)obj[0];
time = 0;
for (int i = 0; i < 10; i++) {
long start = Clock.getInstance().now();
Object keys[] = KeyGenerator.getInstance().generateSigningKeypair();
long end = Clock.getInstance().now();
Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey)keys[1]);
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey)keys[0]);
boolean fakeOk = DSAEngine.getInstance().verifySignature(sig, src, fake);
time += end - start;
log.debug("V(S(data)) == " + ok + " fake verify correctly failed? " + (fakeOk == false));
}
log.info("Signing Keygen 10 times: " + time + "ms");
time = 0;
for (int i = 0; i < 1000; i++) {
long start = Clock.getInstance().now();
KeyGenerator.getInstance().generateSessionKey();
long end = Clock.getInstance().now();
time += end - start;
}
log.info("Session keygen 1000 times: " + time + "ms");
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
}
}

View File

@ -0,0 +1,164 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
/**
* Expose the functionality to allow people to write out and read in the
* session key and session tag information via streams. This implementation
* does not write anywhere except where its told.
*
*/
public class PersistentSessionKeyManager extends TransientSessionKeyManager {
private final static Log _log = new Log(PersistentSessionKeyManager.class);
private Object _yk = YKGenerator.class;
/**
* Write the session key data to the given stream
*
*/
public void saveState(OutputStream out) throws IOException, DataFormatException {
Set tagSets = getInboundTagSets();
Set sessions = getOutboundSessions();
_log.info("Saving state with " + tagSets.size() + " inbound tagSets and " + sessions.size() + " outbound sessions");
DataHelper.writeLong(out, 4, tagSets.size());
for (Iterator iter = tagSets.iterator(); iter.hasNext(); ) {
TagSet ts = (TagSet)iter.next();
writeTagSet(out, ts);
}
DataHelper.writeLong(out, 4, sessions.size());
for (Iterator iter = sessions.iterator(); iter.hasNext(); ) {
OutboundSession sess = (OutboundSession)iter.next();
writeOutboundSession(out, sess);
}
}
/**
* Load the session key data from the given stream
*
*/
public void loadState(InputStream in) throws IOException, DataFormatException {
int inboundSets = (int)DataHelper.readLong(in, 4);
Set tagSets = new HashSet(inboundSets);
for (int i = 0; i < inboundSets; i++) {
TagSet ts = readTagSet(in);
tagSets.add(ts);
}
int outboundSessions = (int)DataHelper.readLong(in, 4);
Set sessions = new HashSet(outboundSessions);
for (int i = 0; i < outboundSessions; i++) {
OutboundSession sess = readOutboundSession(in);
sessions.add(sess);
}
_log.info("Loading state with " + tagSets.size() + " inbound tagSets and " + sessions.size() + " outbound sessions");
setData(tagSets, sessions);
}
private void writeOutboundSession(OutputStream out, OutboundSession sess) throws IOException, DataFormatException {
sess.getTarget().writeBytes(out);
sess.getCurrentKey().writeBytes(out);
DataHelper.writeDate(out, new Date(sess.getEstablishedDate()));
DataHelper.writeDate(out, new Date(sess.getLastUsedDate()));
List sets = sess.getTagSets();
DataHelper.writeLong(out, 2, sets.size());
for (Iterator iter = sets.iterator(); iter.hasNext(); ) {
TagSet set = (TagSet)iter.next();
writeTagSet(out, set);
}
}
private void writeTagSet(OutputStream out, TagSet ts) throws IOException, DataFormatException {
ts.getAssociatedKey().writeBytes(out);
DataHelper.writeDate(out, new Date(ts.getDate()));
DataHelper.writeLong(out, 2, ts.getTags().size());
for (Iterator iter = ts.getTags().iterator(); iter.hasNext(); ) {
SessionTag tag = (SessionTag)iter.next();
out.write(tag.getData());
}
}
private OutboundSession readOutboundSession(InputStream in) throws IOException, DataFormatException {
PublicKey key = new PublicKey();
key.readBytes(in);
SessionKey skey = new SessionKey();
skey.readBytes(in);
Date established = DataHelper.readDate(in);
Date lastUsed = DataHelper.readDate(in);
int tagSets = (int)DataHelper.readLong(in, 2);
ArrayList sets = new ArrayList(tagSets);
for (int i = 0; i < tagSets; i++) {
TagSet ts = readTagSet(in);
sets.add(ts);
}
return new OutboundSession(key, skey, established.getTime(), lastUsed.getTime(), sets);
}
private TagSet readTagSet(InputStream in) throws IOException, DataFormatException {
SessionKey key = new SessionKey();
key.readBytes(in);
Date date = DataHelper.readDate(in);
int numTags = (int)DataHelper.readLong(in, 2);
Set tags = new HashSet(numTags);
for (int i = 0; i < numTags; i++) {
SessionTag tag = new SessionTag();
byte val[] = new byte[SessionTag.BYTE_LENGTH];
int read = DataHelper.read(in, val);
if (read != SessionTag.BYTE_LENGTH)
throw new IOException("Unable to fully read a session tag [" + read + " not " + SessionTag.BYTE_LENGTH + ")");
tag.setData(val);
tags.add(tag);
}
TagSet ts = new TagSet(tags, key);
ts.setDate(date.getTime());
return ts;
}
public static void main(String args[]) {
PersistentSessionKeyManager mgr = new PersistentSessionKeyManager();
try {
mgr.loadState(new FileInputStream("sessionKeys.dat"));
String state = mgr.renderStatusHTML();
FileOutputStream fos = new FileOutputStream("sessionKeysBeforeExpire.html");
fos.write(state.getBytes());
fos.close();
int expired = mgr.aggressiveExpire();
_log.error("Expired: " + expired);
String stateAfter = mgr.renderStatusHTML();
FileOutputStream fos2 = new FileOutputStream("sessionKeysAfterExpire.html");
fos2.write(stateAfter.getBytes());
fos2.close();
} catch (Throwable t) {
_log.error("Error loading/storing sessionKeys", t);
}
try { Thread.sleep(3000); } catch (Throwable t) {}
}
}

View File

@ -0,0 +1,173 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the TheCrypto may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import net.i2p.data.Hash;
/** Defines a wrapper for SHA-256 operation
*
* This is done. Takes data of any size and hashes it.
*
* @author thecrypto,jrandom
*/
public class SHA256Generator {
private static SHA256Generator _generator = new SHA256Generator();
public static SHA256Generator getInstance() { return _generator; }
static int[] K = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
static int[] H0 = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
/** Calculate the SHA-256 has of the source
* @param source what to hash
* @return hash of the source
*/
public Hash calculateHash(byte[] source) {
long length = source.length * 8;
int k = 448 - (int)((length+1) % 512);
if (k < 0) {
k += 512;
}
int padbytes = k/8;
int wordlength = (int)(source.length/4 + padbytes/4 + 3);
int[] M0 = new int[wordlength];
int wordcount = 0;
int x = 0;
for (x = 0; x < (source.length / 4) * 4; x += 4) {
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= source[x + 3] << 24 >>> 24 << 0;
wordcount++;
}
switch (source.length - (wordcount + 1) * 4 + 4) {
case 0:
M0[wordcount] |= 0x80000000;
break;
case 1:
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= 0x00800000;
break;
case 2:
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= 0x00008000;
break;
case 3:
M0[wordcount] = source[ x ] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= 0x00000080;
break;
}
M0[wordlength - 2] = (int)(length >>> 32);
M0[wordlength - 1] = (int)(length);
int[] H = new int[8];
for (x = 0; x < 8; x++) {
H[x] = H0[x];
}
int blocks = M0.length/16;
for (int bl = 0; bl < blocks; bl++) {
int a = H[0]; int b = H[1]; int c = H[2]; int d = H[3];
int e = H[4]; int f = H[5]; int g = H[6]; int h = H[7];
int[] W = new int[64];
for (x = 0; x < 64; x++) {
if (x < 16) {
W[x] = M0[bl*16 + x];
} else {
W[x] = add(o1(W[x-2]), add(W[x-7], add(o0(W[x-15]), W[x-16])));
}
}
for (x = 0; x < 64; x++) {
int T1 = add(h, add(e1(e), add(Ch(e, f, g), add(K[x], W[x]))));
int T2 = add(e0(a), Maj(a, b, c));
h = g; g = f; f = e; e = add(d, T1); d = c; c = b; b = a; a = add(T1, T2);
}
H[0] = add(a, H[0]); H[1] = add(b, H[1]); H[2] = add(c, H[2]); H[3] = add(d, H[3]);
H[4] = add(e, H[4]); H[5] = add(f, H[5]); H[6] = add(g, H[6]); H[7] = add(h, H[7]);
}
byte[] hashbytes = new byte[32];
for (x = 0; x < 8; x++) {
hashbytes[ x * 4 ] = (byte)(H[x] << 0 >>> 24);
hashbytes[x * 4 + 1] = (byte)(H[x] << 8 >>> 24);
hashbytes[x * 4 + 2] = (byte)(H[x] << 16 >>> 24);
hashbytes[x * 4 + 3] = (byte)(H[x] << 24 >>> 24);
}
Hash hash = new Hash();
hash.setData(hashbytes);
return hash;
}
private static int Ch(int x, int y, int z) {
return (x & y) ^ (~x & z);
}
private static int Maj(int x, int y, int z) {
return (x & y) ^ (x & z) ^ (y & z);
}
private static int ROTR (int x, int n) {
return (x >>> n) | (x << 32 - n);
}
private static int e0(int x) {
return ROTR(x, 2) ^ ROTR (x, 13) ^ ROTR(x, 22);
}
private static int e1(int x) {
return ROTR(x, 6) ^ ROTR (x, 11) ^ ROTR(x, 25);
}
private static int SHR(int x, int n) {
return x >>> n;
}
private static int o0(int x) {
return ROTR(x, 7) ^ ROTR (x, 18) ^ SHR(x, 3);
}
private static int o1(int x) {
return ROTR(x, 17) ^ ROTR (x, 19) ^ SHR(x, 10);
}
private static int add (int x, int y) {
return x+y;
}
}

View File

@ -0,0 +1,111 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import java.util.Set;
/**
* Manage the session keys and session tags used for encryption and decryption.
* This base implementation simply ignores sessions and acts as if everything is
* unknown (and hence always forces a full ElGamal encryption for each message).
* A more intelligent subclass should manage and persist keys and tags.
*
*/
public class SessionKeyManager {
private final static SessionKeyManager _instance = new PersistentSessionKeyManager(); // new TransientSessionKeyManager(); // SessionKeyManager();
public final static SessionKeyManager getInstance() { return _instance; }
/**
* Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated.
*
*/
public SessionKey getCurrentKey(PublicKey target) { return null; }
/**
* Associate a new session key with the specified target. Metrics to determine
* when to expire that key begin with this call.
*
*/
public void createSession(PublicKey target, SessionKey key) { }
/**
* Generate a new session key and associate it with the specified target.
*
*/
public SessionKey createSession(PublicKey target) {
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
createSession(target, key);
return key;
}
/**
* Retrieve the next available session tag for identifying the use of the given
* key when communicating with the target. If this returns null, no tags are
* available so ElG should be used with the given key (a new sessionKey should
* NOT be used)
*
*/
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) { return null; }
/**
* Determine (approximately) how many available session tags for the current target
* have been confirmed and are available
*
*/
public int getAvailableTags(PublicKey target, SessionKey key) { return 0; }
/**
* Determine how long the available tags will be available for before expiring, in
* milliseconds
*/
public long getAvailableTimeLeft(PublicKey target, SessionKey key) { return 0; }
/**
* Take note of the fact that the given sessionTags associated with the key for
* encryption to the target have definitely been received at the target (aka call this
* method after receiving an ack to a message delivering them)
*
*/
public void tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) { }
/**
* Mark all of the tags delivered to the target up to this point as invalid, since the peer
* has failed to respond when they should have. This call essentially lets the system recover
* from corrupted tag sets and crashes
*
*/
public void failTags(PublicKey target) {}
/**
* Accept the given tags and associate them with the given key for decryption
*
*/
public void tagsReceived(SessionKey key, Set sessionTags) { }
/**
* Determine if we have received a session key associated with the given session tag,
* and if so, discard it (but keep track for frequent dups) and return the decryption
* key it was received with (via tagsReceived(...)). returns null if no session key
* matches
*
*/
public SessionKey consumeTag(SessionTag tag) { return null; }
/**
* Called when the system is closing down, instructing the session key manager to take
* whatever precautions are necessary (saving state, etc)
*
*/
public void shutdown() {}
}

View File

@ -0,0 +1,527 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Date;
/**
* Implement the session key management, but keep everything in memory (don't write
* to disk). However, this being java, we cannot guarantee that the keys aren't swapped
* out to disk so this should not be considered secure in that sense.
*
*/
class TransientSessionKeyManager extends SessionKeyManager {
private final static Log _log = new Log(TransientSessionKeyManager.class);
private Map _outboundSessions; // PublicKey --> OutboundSession
private Map _inboundTagSets; // SessionTag --> TagSet
/**
* Let session tags sit around for 10 minutes before expiring them. We can now have such a large
* value since there is the persistent session key manager. This value is for outbound tags -
* inbound tags are managed by SESSION_LIFETIME_MAX_MS
*
*/
public final static long SESSION_TAG_DURATION_MS = 10*60*1000;
/**
* Keep unused inbound session tags around for up to 15 minutes (5 minutes longer than
* session tags are used on the outbound side so that no reasonable network lag
* can cause failed decrypts)
*
*/
public final static long SESSION_LIFETIME_MAX_MS = SESSION_TAG_DURATION_MS + 5*60*1000;
public final static int MAX_INBOUND_SESSION_TAGS = 100*1000; // this will consume at most 3.2M
public TransientSessionKeyManager() {
super();
_outboundSessions = new HashMap(64);
_inboundTagSets = new HashMap(1024);
}
/** TagSet */
protected Set getInboundTagSets() {
synchronized (_inboundTagSets) {
return new HashSet(_inboundTagSets.values());
}
}
/** OutboundSession */
protected Set getOutboundSessions() {
synchronized (_outboundSessions) {
return new HashSet(_outboundSessions.values());
}
}
protected void setData(Set inboundTagSets, Set outboundSessions) {
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and " + outboundSessions.size() + " outbound sessions");
Map tagSets = new HashMap(inboundTagSets.size());
for (Iterator iter = inboundTagSets.iterator(); iter.hasNext(); ) {
TagSet ts = (TagSet)iter.next();
for (Iterator tsIter = ts.getTags().iterator(); tsIter.hasNext(); ) {
SessionTag tag = (SessionTag)tsIter.next();
tagSets.put(tag, ts);
}
}
synchronized (_inboundTagSets) {
_inboundTagSets.clear();
_inboundTagSets.putAll(tagSets);
}
Map sessions = new HashMap(outboundSessions.size());
for (Iterator iter = outboundSessions.iterator(); iter.hasNext(); ) {
OutboundSession sess = (OutboundSession)iter.next();
sessions.put(sess.getTarget(), sess);
}
synchronized (_outboundSessions) {
_outboundSessions.clear();
_outboundSessions.putAll(sessions);
}
}
/**
* Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated.
*
*/
public SessionKey getCurrentKey(PublicKey target) {
OutboundSession sess = getSession(target);
if (sess == null) return null;
long now = Clock.getInstance().now();
if (sess.getEstablishedDate() < now - SESSION_LIFETIME_MAX_MS) {
_log.info("Expiring old session key established on " + new Date(sess.getEstablishedDate()) + " with target " + target);
return null;
} else {
return sess.getCurrentKey();
}
}
/**
* Associate a new session key with the specified target. Metrics to determine
* when to expire that key begin with this call.
*
*/
public void createSession(PublicKey target, SessionKey key) {
OutboundSession sess = new OutboundSession(target);
sess.setCurrentKey(key);
addSession(sess);
}
/**
* Retrieve the next available session tag for identifying the use of the given
* key when communicating with the target. If this returns null, no tags are
* available so ElG should be used with the given key (a new sessionKey should
* NOT be used)
*
*/
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
OutboundSession sess = getSession(target);
if (sess == null) {
_log.debug("No session for " + target);
return null;
}
if (sess.getCurrentKey().equals(key)) {
SessionTag nxt = sess.consumeNext();
_log.debug("Tag consumed: " + nxt);
return nxt;
} else {
_log.debug("Key does not match existing key, no tag");
return null;
}
}
/**
* Determine (approximately) how many available session tags for the current target
* have been confirmed and are available
*
*/
public int getAvailableTags(PublicKey target, SessionKey key) {
OutboundSession sess = getSession(target);
if (sess == null) {
return 0;
}
if (sess.getCurrentKey().equals(key)) {
return sess.availableTags();
} else {
return 0;
}
}
/**
* Determine how long the available tags will be available for before expiring, in
* milliseconds
*/
public long getAvailableTimeLeft(PublicKey target, SessionKey key) {
OutboundSession sess = getSession(target);
if (sess == null) {
return 0;
}
if (sess.getCurrentKey().equals(key)) {
return (sess.getLastExpirationDate() + SESSION_TAG_DURATION_MS) - Clock.getInstance().now();
} else {
return 0;
}
}
/**
* Take note of the fact that the given sessionTags associated with the key for
* encryption to the target have definitely been received at the target (aka call this
* method after receiving an ack to a message delivering them)
*
*/
public void tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) {
OutboundSession sess = getSession(target);
if (sess == null) {
createSession(target, key);
sess = getSession(target);
}
sess.setCurrentKey(key);
TagSet set = new TagSet(sessionTags, key);
sess.addTags(set);
_log.debug("Tags delivered to set " + set + " on session " + sess);
}
/**
* Mark all of the tags delivered to the target up to this point as invalid, since the peer
* has failed to respond when they should have. This call essentially lets the system recover
* from corrupted tag sets and crashes
*
*/
public void failTags(PublicKey target) {
removeSession(target);
}
/**
* Accept the given tags and associate them with the given key for decryption
*
*/
public void tagsReceived(SessionKey key, Set sessionTags) {
TagSet tagSet = new TagSet(sessionTags, key);
for (Iterator iter = sessionTags.iterator(); iter.hasNext(); ) {
SessionTag tag = (SessionTag)iter.next();
_log.debug("Receiving tag " + tag + " for key " + key);
synchronized (_inboundTagSets) {
_inboundTagSets.put(tag, tagSet);
}
}
synchronized (_inboundTagSets) {
// todo: make this limit the tags by sessionKey and actually enforce the limit!
int overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS;
if (overage > 0) {
_log.error("TOO MANY SESSION TAGS! " + (_inboundTagSets.size()));
}
}
if (sessionTags.size() <= 0)
_log.debug("Received 0 tags for key " + key);
}
/**
* Determine if we have received a session key associated with the given session tag,
* and if so, discard it (but keep track for frequent dups) and return the decryption
* key it was received with (via tagsReceived(...)). returns null if no session key
* matches
*
*/
public SessionKey consumeTag(SessionTag tag) {
synchronized (_inboundTagSets) {
TagSet tagSet = (TagSet)_inboundTagSets.remove(tag);
if (tagSet == null) {
_log.debug("Cannot consume tag " + tag + " as it is not known");
return null;
} else {
tagSet.consume(tag);
}
SessionKey key = tagSet.getAssociatedKey();
_log.debug("Consuming tag " + tag + " for sessionKey " + key);
return key;
}
}
private OutboundSession getSession(PublicKey target) {
synchronized (_outboundSessions) {
return (OutboundSession)_outboundSessions.get(target);
}
}
private void addSession(OutboundSession sess) {
synchronized (_outboundSessions) {
_outboundSessions.put(sess.getTarget(), sess);
}
}
private void removeSession(PublicKey target) {
if (target == null) return;
synchronized (_outboundSessions) {
_outboundSessions.remove(target);
}
}
/**
* Aggressively expire inbound tag sets and outbound sessions
*
* @return number of tag sets expired
*/
public int aggressiveExpire() {
int removed = 0;
long now = Clock.getInstance().now();
Set tagsToDrop = new HashSet(64);
synchronized (_inboundTagSets) {
for (Iterator iter = _inboundTagSets.keySet().iterator(); iter.hasNext(); ) {
SessionTag tag = (SessionTag)iter.next();
TagSet ts = (TagSet)_inboundTagSets.get(tag);
if (ts.getDate() < now - SESSION_LIFETIME_MAX_MS) {
tagsToDrop.add(tag);
}
}
removed += tagsToDrop.size();
for (Iterator iter = tagsToDrop.iterator(); iter.hasNext(); )
_inboundTagSets.remove(iter.next());
}
//_log.warn("Expiring tags: [" + tagsToDrop + "]");
synchronized (_outboundSessions) {
Set sessionsToDrop = new HashSet(64);
for (Iterator iter = _outboundSessions.keySet().iterator(); iter.hasNext(); ) {
PublicKey key = (PublicKey)iter.next();
OutboundSession sess = (OutboundSession)_outboundSessions.get(key);
removed += sess.expireTags();
if (sess.getTagSets().size() <= 0)
sessionsToDrop.add(key);
}
for (Iterator iter = sessionsToDrop.iterator(); iter.hasNext(); )
_outboundSessions.remove(iter.next());
}
return removed;
}
public String renderStatusHTML() {
StringBuffer buf = new StringBuffer(1024);
buf.append("<h2>Inbound sessions</h2>");
buf.append("<table border=\"1\">");
Set inbound = getInboundTagSets();
Map inboundSets = new HashMap(inbound.size());
for (Iterator iter = inbound.iterator(); iter.hasNext(); ) {
TagSet ts = (TagSet)iter.next();
if (!inboundSets.containsKey(ts.getAssociatedKey()))
inboundSets.put(ts.getAssociatedKey(), new HashSet());
Set sets = (Set)inboundSets.get(ts.getAssociatedKey());
sets.add(ts);
}
for (Iterator iter = inboundSets.keySet().iterator(); iter.hasNext(); ) {
SessionKey skey = (SessionKey)iter.next();
Set sets = (Set)inboundSets.get(skey);
buf.append("<tr><td><b>Session key</b>: ").append(skey.toBase64()).append("</td>");
buf.append("<td><b># Sets:</b> ").append(sets.size()).append("</td></tr>");
buf.append("<tr><td colspan=\"2\"><ul>");
for (Iterator siter = sets.iterator(); siter.hasNext(); ) {
TagSet ts = (TagSet)siter.next();
buf.append("<li><b>Received on:</b> ").append(new Date(ts.getDate())).append(" with ").append(ts.getTags().size()).append(" tags remaining</li>");
}
buf.append("</ul></td></tr>");
}
buf.append("</table>");
buf.append("<h2><b>Outbound sessions</b></h2>");
buf.append("<table border=\"1\">");
Set outbound = getOutboundSessions();
for (Iterator iter = outbound.iterator(); iter.hasNext(); ) {
OutboundSession sess = (OutboundSession)iter.next();
buf.append("<tr><td><b>Target key:</b> ").append(sess.getTarget().toString()).append("<br />");
buf.append("<b>Established:</b> ").append(new Date(sess.getEstablishedDate())).append("<br />");
buf.append("<b>Last Used:</b> ").append(new Date(sess.getLastUsedDate())).append("<br />");
buf.append("<b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>");
buf.append("<tr><td><b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</td></tr>");
buf.append("<tr><td><ul>");
for (Iterator siter = sess.getTagSets().iterator(); siter.hasNext(); ) {
TagSet ts = (TagSet)siter.next();
buf.append("<li><b>Sent on:</b> ").append(new Date(ts.getDate())).append(" with ").append(ts.getTags().size()).append(" tags remaining</li>");
}
buf.append("</ul></td></tr>");
}
buf.append("</table>");
return buf.toString();
}
static class OutboundSession {
private PublicKey _target;
private SessionKey _currentKey;
private long _established;
private long _lastUsed;
private List _tagSets;
public OutboundSession(PublicKey target) {
this(target, null, Clock.getInstance().now(), Clock.getInstance().now(), new ArrayList());
}
OutboundSession(PublicKey target, SessionKey curKey, long established, long lastUsed, List tagSets) {
_target = target;
_currentKey = curKey;
_established = established;
_lastUsed = lastUsed;
_tagSets = tagSets;
}
/** list of TagSet objects */
List getTagSets() {
synchronized (_tagSets) {
return new ArrayList(_tagSets);
}
}
public PublicKey getTarget() { return _target; }
public SessionKey getCurrentKey() { return _currentKey; }
public void setCurrentKey(SessionKey key) {
if (_currentKey != null) {
if (!_currentKey.equals(key)) {
int dropped = 0;
List sets = _tagSets;
_tagSets = new ArrayList();
for (int i = 0; i < sets.size(); i++) {
TagSet set = (TagSet)sets.get(i);
dropped += set.getTags().size();
}
_log.info("Rekeyed from " + _currentKey + " to " + key + ": dropping " + dropped + " session tags");
}
}
_currentKey = key;
}
public long getEstablishedDate() { return _established; }
public long getLastUsedDate() { return _lastUsed; }
/**
* Expire old tags, returning the number of tag sets removed
*/
public int expireTags() {
long now = Clock.getInstance().now();
Set toRemove = new HashSet(64);
synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet)_tagSets.get(i);
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
toRemove.add(set);
}
}
_tagSets.removeAll(toRemove);
}
return toRemove.size();
}
public SessionTag consumeNext() {
long now = Clock.getInstance().now();
synchronized (_tagSets) {
while (_tagSets.size() > 0) {
TagSet set = (TagSet)_tagSets.get(0);
if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
SessionTag tag = set.consumeNext();
if (tag != null) return tag;
} else {
_log.info("TagSet from " + new Date(set.getDate()) + " expired");
}
_tagSets.remove(0);
}
}
return null;
}
public int availableTags() {
int tags = 0;
synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet)_tagSets.get(i);
tags += set.getTags().size();
}
}
return tags;
}
/**
* Get the furthest away tag set expiration date - after which all of the
* tags will have expired
*
*/
public long getLastExpirationDate() {
long last = 0;
synchronized (_tagSets) {
for (Iterator iter = _tagSets.iterator(); iter.hasNext(); ) {
TagSet set = (TagSet)iter.next();
if (set.getDate() > last)
last = set.getDate();
}
}
return last + SESSION_TAG_DURATION_MS;
}
public void addTags(TagSet set) {
synchronized (_tagSets) {
_tagSets.add(set);
}
}
}
static class TagSet {
private Set _sessionTags;
private SessionKey _key;
private long _date;
public TagSet(Set tags, SessionKey key) {
if (key == null)
throw new IllegalArgumentException("Missing key");
if (tags == null)
throw new IllegalArgumentException("Missing tags");
_sessionTags = tags;
_key = key;
_date = Clock.getInstance().now();
}
public long getDate() { return _date; }
void setDate(long when) { _date = when; }
public Set getTags() { return _sessionTags; }
public SessionKey getAssociatedKey() { return _key; }
public boolean contains(SessionTag tag) { return _sessionTags.contains(tag); }
public void consume(SessionTag tag) {
if (contains(tag)) {
_sessionTags.remove(tag);
}
}
public SessionTag consumeNext() {
if (_sessionTags.size() <= 0) {
return null;
} else {
SessionTag first = (SessionTag)_sessionTags.iterator().next();
_sessionTags.remove(first);
return first;
}
}
public int hashCode() {
long rv = 0;
if (_key != null)
rv = rv*7 + _key.hashCode();
rv = rv*7 + _date;
if (_sessionTags != null)
rv = rv*7 + DataHelper.hashCode(_sessionTags);
return (int)rv;
}
public boolean equals(Object o) {
if ( (o == null) || !(o instanceof TagSet) ) return false;
TagSet ts = (TagSet)o;
return DataHelper.eq(ts.getAssociatedKey(), getAssociatedKey()) &&
DataHelper.eq(ts.getTags(), getTags()) &&
ts.getDate() == getDate();
}
}
}

View File

@ -0,0 +1,193 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
/**
* Precalculate the Y and K for ElGamal encryption operations.
*
* This class precalcs a set of values on its own thread, using those transparently
* when a new instance is created. By default, the minimum threshold for creating
* new values for the pool is 5, and the max pool size is 10. Whenever the pool has
* less than the minimum, it fills it up again to the max. There is a delay after
* each precalculation so that the CPU isn't hosed during startup (defaulting to 10 seconds).
* These three parameters are controlled by java environmental variables and
* can be adjusted via:
* -Dcrypto.yk.precalc.min=40 -Dcrypto.yk.precalc.max=100 -Dcrypto.yk.precalc.delay=60000
*
* (delay is milliseconds)
*
* To disable precalculation, set min to 0
*
* @author jrandom
*/
class YKGenerator {
private final static Log _log = new Log(YKGenerator.class);
private static int MIN_NUM_BUILDERS = -1;
private static int MAX_NUM_BUILDERS = -1;
private static int CALC_DELAY = -1;
private static volatile List _values = new ArrayList(50); // list of BigInteger[] values (y and k)
private static Thread _precalcThread = null;
public final static String PROP_YK_PRECALC_MIN = "crypto.yk.precalc.min";
public final static String PROP_YK_PRECALC_MAX = "crypto.yk.precalc.max";
public final static String PROP_YK_PRECALC_DELAY = "crypto.yk.precalc.delay";
public final static String DEFAULT_YK_PRECALC_MIN = "10";
public final static String DEFAULT_YK_PRECALC_MAX = "30";
public final static String DEFAULT_YK_PRECALC_DELAY = "10000";
/** check every 30 seconds whether we have less than the minimum */
private final static long CHECK_DELAY = 30*1000;
static {
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN));
MIN_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MIN);
MIN_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX));
MAX_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MAX);
MAX_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY));
CALC_DELAY = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_DELAY);
CALC_DELAY = val;
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " + CALC_DELAY + ")");
_precalcThread = new Thread(new YKPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS));
_precalcThread.setName("YK Precalc");
_precalcThread.setDaemon(true);
_precalcThread.setPriority(Thread.MIN_PRIORITY);
_precalcThread.start();
}
private static final int getSize() { synchronized (_values) { return _values.size(); } }
private static final int addValues(BigInteger yk[]) {
int sz = 0;
synchronized (_values) {
_values.add(yk);
sz = _values.size();
}
return sz;
}
public static BigInteger[] getNextYK() {
if (true) {
synchronized (_values) {
if (_values.size() > 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Sufficient precalculated YK values - fetch the existing");
return (BigInteger[])_values.remove(0);
}
}
}
if (_log.shouldLog(Log.INFO)) _log.info("Insufficient precalculated YK values - create a new one");
return generateYK();
}
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02 } );
private static final BigInteger[] generateYK() {
NativeBigInteger k = null;
BigInteger y = null;
long t0 = 0;
long t1 = 0;
while (k == null) {
t0 = Clock.getInstance().now();
k = new NativeBigInteger(2048, RandomSource.getInstance());
t1 = Clock.getInstance().now();
if (BigInteger.ZERO.compareTo(k) == 0) {
k = null;
continue;
}
BigInteger kPlus2 = k.add(_two);
if (kPlus2.compareTo(CryptoConstants.elgp) > 0)
k = null;
}
long t2 = Clock.getInstance().now();
y = CryptoConstants.elgg.modPow(k, CryptoConstants.elgp);
BigInteger yk[] = new BigInteger[2];
yk[0] = y;
yk[1] = k;
long diff = t2 - t0;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to generate YK value for ElGamal (" + diff + "ms)");
}
return yk;
}
public static void main(String args[]) {
RandomSource.getInstance().nextBoolean(); // warm it up
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
_log.debug("\n\n\n\nBegin test\n");
long negTime = 0;
for (int i = 0; i < 5; i++) {
long startNeg = Clock.getInstance().now();
getNextYK();
long endNeg = Clock.getInstance().now();
}
_log.debug("YK fetch time for 5 runs: " + negTime + " @ " + negTime/5l + "ms each");
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
private static class YKPrecalcRunner implements Runnable {
private int _minSize;
private int _maxSize;
private YKPrecalcRunner(int minSize, int maxSize) {
_minSize = minSize;
_maxSize = maxSize;
}
public void run() {
while (true) {
int curSize = 0;
long start = Clock.getInstance().now();
int startSize = getSize();
curSize = startSize;
while (curSize < _minSize) {
while (curSize < _maxSize) {
long begin = Clock.getInstance().now();
curSize = addValues(generateYK());
long end = Clock.getInstance().now();
if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalculated YK value in " + (end-begin) + "ms");
// for some relief...
try { Thread.sleep(CALC_DELAY); } catch (InterruptedException ie) {}
}
}
long end = Clock.getInstance().now();
int numCalc = curSize - startSize;
if (numCalc > 0) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalced " + numCalc + " to " + curSize + " in " + (end-start-CALC_DELAY*numCalc) + "ms (not counting " + (CALC_DELAY*numCalc) + "ms relief). now sleeping");
}
try { Thread.sleep(CHECK_DELAY); } catch (InterruptedException ie) {}
}
}
}
}

View File

@ -0,0 +1,642 @@
package net.i2p.data;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Encodes and decodes to and from Base64 notation.
*
* <p>
* Change Log:
* </p>
* <ul>
* <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
* <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
* where last buffer being read, if not completely full, was not returned.</li>
* <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
* <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
* </ul>
*
* <p>
* I am placing this code in the Public Domain. Do with it as you will.
* This software comes with no guarantees or warranties but with
* plenty of well-wishing instead!
* Please visit <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
* periodically to check for updates or to contribute improvements.
* </p>
*
* Modified by jrandom for i2p, using safeEncode / safeDecode to create filesystem and URL safe
* base64 values (replacing / with ~, and + with -)
*
* @author Robert Harder
* @author rob@iharder.net
* @version 1.3.4
*/
public class Base64
{
public static String encode(byte[] source) { return safeEncode(source); }
public static byte[] decode(String s) { return safeDecode(s); }
/** Maximum line length (76) of Base64 output. */
private final static int MAX_LINE_LENGTH = 76;
/** The equals sign (=) as a byte. */
private final static byte EQUALS_SIGN = (byte)'=';
/** The new line character (\n) as a byte. */
private final static byte NEW_LINE = (byte)'\n';
/** The 64 valid Base64 values. */
private final static byte[] ALPHABET =
{
(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
(byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
(byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
(byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
(byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
(byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
(byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
};
/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
**/
private final static byte[] DECODABET =
{
-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
-5,-5, // Whitespace: Tab and Linefeed
-9,-9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
-9,-9,-9,-9,-9, // Decimal 27 - 31
-5, // Whitespace: Space
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
62, // Plus sign at decimal 43
-9,-9,-9, // Decimal 44 - 46
63, // Slash at decimal 47
52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
-9,-9,-9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9,-9,-9, // Decimal 62 - 64
0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
-9,-9,-9,-9,-9,-9, // Decimal 91 - 96
26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
-9,-9,-9,-9 // Decimal 123 - 126
/*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
private final static byte BAD_ENCODING = -9; // Indicates error in encoding
private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
/** Defeats instantiation. */
private Base64(){}
public static void main(String[] args)
{
if (args.length == 0) {
help();
return;
}
runApp(args);
}
private static void runApp(String args[]) {
try {
InputStream in = System.in;
OutputStream out = System.out;
if (args.length >= 3) {
out = new FileOutputStream(args[2]);
}
if (args.length >= 2) {
in = new FileInputStream(args[1]);
}
if ("encode".equalsIgnoreCase(args[0])) {
encode(in, out);
return;
}
if ("decode".equalsIgnoreCase(args[0])) {
decode(in, out);
return;
}
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
}
}
private static byte[] read(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
byte buf[] = new byte[4096];
while (true) {
int read = in.read(buf);
if (read < 0)
break;
baos.write(buf, 0, read);
}
return baos.toByteArray();
}
private static void encode(InputStream in, OutputStream out) throws IOException {
String encoded = encode(read(in));
out.write(encoded.getBytes());
}
private static void decode(InputStream in, OutputStream out) throws IOException {
byte decoded[] = decode(new String(read(in)));
out.write(decoded);
}
private static void help() {
System.out.println("Syntax: Base64 encode <inFile> <outFile>");
System.out.println("or : Base64 encode <inFile>");
System.out.println("or : Base64 encode");
System.out.println("or : Base64 decode <inFile> <outFile>");
System.out.println("or : Base64 decode <inFile>");
System.out.println("or : Base64 decode");
System.out.println("or : Base64 test");
}
private static void test() {
String orig = "you smell";
String encoded = Base64.encode(orig.getBytes());
System.out.println("Encoded: [" + encoded + "]");
byte decoded[] = Base64.decode(encoded);
String transformed = new String(decoded);
if (orig.equals(transformed))
System.out.println("D(E('you smell')) == 'you smell'");
else
throw new RuntimeException("D(E('you smell')) != 'you smell'!!! transformed = [" + transformed + "]");
byte all[] = new byte[256];
for (int i = 0; i < all.length; i++)
all[i] = (byte)(0xFF & i);
encoded = Base64.encode(all);
System.out.println("Encoded: [" + encoded + "]");
decoded = Base64.decode(encoded);
if (DataHelper.eq(decoded, all))
System.out.println("D(E([all bytes])) == [all bytes]");
else
throw new RuntimeException("D(E([all bytes])) != [all bytes]!!!");
}
/* ******** E N C O D I N G M E T H O D S ******** */
/**
* Encodes the first three bytes of array <var>threeBytes</var>
* and returns a four-byte array in Base64 notation.
*
* @param threeBytes the array to convert
* @return four byte array in Base64 notation.
* @since 1.3
*/
private static byte[] encode3to4( byte[] threeBytes )
{ return encode3to4( threeBytes, 3 );
} // end encodeToBytes
/**
* Encodes up to the first three bytes of array <var>threeBytes</var>
* and returns a four-byte array in Base64 notation.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
* The array <var>threeBytes</var> needs only be as big as
* <var>numSigBytes</var>.
*
* @param threeBytes the array to convert
* @param numSigBytes the number of significant bytes in your array
* @return four byte array in Base64 notation.
* @since 1.3
*/
private static byte[] encode3to4( byte[] threeBytes, int numSigBytes )
{ byte[] dest = new byte[4];
encode3to4( threeBytes, 0, numSigBytes, dest, 0 );
return dest;
}
/**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accomodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @return the <var>destination</var> array
* @since 1.3
*/
private static byte[] encode3to4(
byte[] source, int srcOffset, int numSigBytes,
byte[] destination, int destOffset )
{
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index ALPHABET
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
| ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
| ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
switch( numSigBytes )
{
case 3:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
return destination;
case 2:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
destination[ destOffset + 3 ] = EQUALS_SIGN;
return destination;
case 1:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = EQUALS_SIGN;
destination[ destOffset + 3 ] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4
/**
* Encodes a byte array into Base64 notation.
* Equivalen to calling
* <code>encodeBytes( source, 0, source.length )</code>
*
* @param source The data to convert
* @since 1.4
*/
private static String encodeBytes( byte[] source )
{
return encodeBytes( source, false ); // don't add newlines
} // end encodeBytes
/**
* Same as encodeBytes, except uses a filesystem / URL friendly set of characters,
* replacing / with ~, and + with -
*/
private static String safeEncode(byte[] source) {
String encoded = encodeBytes(source);
encoded = encoded.replace('/', '~');
encoded = encoded.replace('+', '-');
return encoded;
}
/**
* Same as decode, except from a filesystem / URL friendly set of characters,
* replacing / with ~, and + with -
*/
private static byte[] safeDecode(String source) {
String toDecode = source.replace('~', '/');
toDecode = toDecode.replace('-', '+');
return standardDecode(toDecode);
}
/**
* Encodes a byte array into Base64 notation.
* Equivalen to calling
* <code>encodeBytes( source, 0, source.length )</code>
*
* @param source The data to convert
* @param breakLines Break lines at 80 characters or less.
* @since 1.4
*/
private static String encodeBytes( byte[] source, boolean breakLines )
{
return encodeBytes( source, 0, source.length, breakLines );
} // end encodeBytes
/**
* Encodes a byte array into Base64 notation.
*
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @since 1.4
*/
private static String encodeBytes( byte[] source, int off, int len )
{
return encodeBytes( source, off, len, true );
} // end encodeBytes
/**
* Encodes a byte array into Base64 notation.
*
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @param breakLines Break lines at 80 characters or less.
* @since 1.4
*/
private static String encodeBytes( byte[] source, int off, int len, boolean breakLines )
{
int len43 = len * 4 / 3;
byte[] outBuff = new byte[ ( len43 ) // Main 4:3
+ ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
+ (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for( ; d < len2; d+=3, e+=4 )
{
encode3to4( source, d+off, 3, outBuff, e );
lineLength += 4;
if( breakLines && lineLength == MAX_LINE_LENGTH )
{
outBuff[e+4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // en dfor: each piece of array
if( d < len )
{
encode3to4( source, d+off, len - d, outBuff, e );
e += 4;
} // end if: some padding needed
return new String( outBuff, 0, e );
} // end encodeBytes
/**
* Encodes a string in Base64 notation with line breaks
* after every 75 Base64 characters.
*
* @param s the string to encode
* @return the encoded string
* @since 1.3
*/
private static String encodeString( String s )
{
return encodeString( s, true );
} // end encodeString
/**
* Encodes a string in Base64 notation with line breaks
* after every 75 Base64 characters.
*
* @param s the string to encode
* @param breakLines Break lines at 80 characters or less.
* @return the encoded string
* @since 1.3
*/
private static String encodeString( String s, boolean breakLines )
{
return encodeBytes( s.getBytes(), breakLines );
} // end encodeString
/* ******** D E C O D I N G M E T H O D S ******** */
/**
* Decodes the first four bytes of array <var>fourBytes</var>
* and returns an array up to three bytes long with the
* decoded values.
*
* @param fourBytes the array with Base64 content
* @return array with decoded values
* @since 1.3
*/
private static byte[] decode4to3( byte[] fourBytes )
{
byte[] outBuff1 = new byte[3];
int count = decode4to3( fourBytes, 0, outBuff1, 0 );
byte[] outBuff2 = new byte[ count ];
for( int i = 0; i < count; i++ )
outBuff2[i] = outBuff1[i];
return outBuff2;
}
/**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accomodate <var>srcOffset</var> + 4 for
* the <var>source</var> array or <var>destOffset</var> + 3 for
* the <var>destination</var> array.
* This method returns the actual number of bytes that
* were converted from the Base64 encoding.
*
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @return the number of decoded bytes converted
* @since 1.3
*/
private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
{
// Example: Dk==
if( source[ srcOffset + 2] == EQUALS_SIGN )
{
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
destination[ destOffset ] = (byte)( outBuff >>> 16 );
return 1;
}
// Example: DkL=
else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
{
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
destination[ destOffset ] = (byte)( outBuff >>> 16 );
destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
return 2;
}
// Example: DkLE
else
{
try{
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
// | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
| ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
destination[ destOffset ] = (byte)( outBuff >> 16 );
destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
destination[ destOffset + 2 ] = (byte)( outBuff );
return 3;
}catch( Exception e){
System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) );
System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) );
System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) );
System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) );
return -1;
} //e nd catch
}
} // end decodeToBytes
/**
* Decodes data from Base64 notation.
*
* @param s the string to decode
* @return the decoded data
* @since 1.4
*/
private static byte[] standardDecode( String s )
{
byte[] bytes = s.getBytes();
return decode( bytes, 0, bytes.length );
} // end decode
/**
* Decodes data from Base64 notation and
* returns it as a string.
* Equivlaent to calling
* <code>new String( decode( s ) )</code>
*
* @param s the strind to decode
* @return The data as a string
* @since 1.4
*/
private static String decodeToString( String s )
{ return new String( decode( s ) );
} // end decodeToString
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base64 encoded data
* @param off The offset of where to begin decoding
* @param len The length of characters to decode
* @return decoded data
* @since 1.3
*/
private static byte[] decode( byte[] source, int off, int len )
{
int len34 = len * 3 / 4;
byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
int outBuffPosn = 0;
byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for( i = 0; i < len; i++ )
{
sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
sbiDecode = DECODABET[ sbiCrop ];
if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
{
if( sbiDecode >= EQUALS_SIGN_ENC )
{
b4[ b4Posn++ ] = sbiCrop;
if( b4Posn > 3 )
{
outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
b4Posn = 0;
// If that was the equals sign, break out of 'for' loop
if( sbiCrop == EQUALS_SIGN )
break;
} // end if: quartet built
} // end if: equals sign or better
} // end if: white space, equals sign or better
else
{
System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
return null;
} // end else:
} // each input character
byte[] out = new byte[ outBuffPosn ];
System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
return out;
} // end decode
} // end class Base64

View File

@ -0,0 +1,46 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.Serializable;
/**
* Wrap up an array of bytes so that they can be compared and placed in hashes,
* maps, and the like.
*
* @author jrandom
*/
public class ByteArray implements Serializable {
private byte[] _data;
public ByteArray() { this(null); }
public ByteArray(byte[] data) { _data = data; }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public boolean equals(Object o) {
if (o == null) return false;
if (o instanceof ByteArray) {
return compare(getData(), ((ByteArray)o).getData());
} else {
try {
byte val[] = (byte[])o;
return compare(getData(), val);
} catch (Throwable t) {
return false;
}
}
}
private boolean compare(byte[] lhs, byte[] rhs) {
return DataHelper.eq(lhs, rhs);
}
public int hashCode() { return DataHelper.hashCode(getData()); }
public String toString() { return DataHelper.toString(getData(), 32); }
}

View File

@ -0,0 +1,115 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines a certificate that can be attached to various I2P structures, such
* as RouterIdentity and Destination, allowing routers and clients to help
* manage denial of service attacks and the network utilization. Certificates
* can even be defined to include identifiable information signed by some
* certificate authority, though that use probably isn't appropriate for an
* anonymous network ;)
*
* @author jrandom
*/
public class Certificate extends DataStructureImpl {
private final static Log _log = new Log(Certificate.class);
private int _type;
private byte[] _payload;
/** Specifies a null certificate type with no payload */
public final static int CERTIFICATE_TYPE_NULL = 0;
/** specifies a Hashcash style certificate */
public final static int CERTIFICATE_TYPE_HASHCASH = 1;
public Certificate() {
_type = 0;
_payload = null;
}
public Certificate(int type, byte[] payload) {
_type = type;
_payload = payload;
}
/** */
public int getCertificateType() { return _type; }
public void setCertificateType(int type) { _type = type; }
public byte[] getPayload() { return _payload; }
public void setPayload(byte[] payload) { _payload = payload; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_type = (int)DataHelper.readLong(in, 1);
int length = (int)DataHelper.readLong(in, 2);
if (length > 0) {
_payload = new byte[length];
int read = read(in, _payload);
if (read != length)
throw new DataFormatException("Not enough bytes for the payload (read: " + read + " length: " + length + ")");
}
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_type < 0)
throw new DataFormatException("Invalid certificate type: " + _type);
if ( (_type != 0) && (_payload == null) )
throw new DataFormatException("Payload is required for non null type");
DataHelper.writeLong(out, 1, (long)_type);
if (_payload != null) {
DataHelper.writeLong(out, 2, (long)_payload.length);
out.write(_payload);
} else {
DataHelper.writeLong(out, 2, 0L);
}
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof Certificate) )
return false;
Certificate cert = (Certificate)object;
return getCertificateType() == cert.getCertificateType() &&
DataHelper.eq(getPayload(), cert.getPayload());
}
public int hashCode() {
return getCertificateType() + DataHelper.hashCode(getPayload());
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[Certificate: type: ");
if (getCertificateType() == CERTIFICATE_TYPE_NULL)
buf.append("Null certificate");
else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH)
buf.append("Hashcash certificate");
else
buf.append("Unknown certificiate type (").append(getCertificateType()).append(")");
if (_payload == null) {
buf.append(" null payload");
} else {
buf.append(" payload size: ").append(_payload.length);
int len = 32;
if (len > _payload.length)
len = _payload.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_payload, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,28 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
import net.i2p.I2PException;
/**
* Thrown when the data was not available to read or write a DataStructure
*
* @author jrandom
*/
public class DataFormatException extends I2PException {
private final static Log _log = new Log(DataFormatException.class);
public DataFormatException(String msg, Throwable t) {
super(msg, t);
}
public DataFormatException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,552 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
/**
* Defines some simple IO routines for dealing with marshalling data structures
*
* @author jrandom
*/
public class DataHelper {
private final static Log _log = new Log(DataHelper.class);
private final static String _equal = "="; // in UTF-8
private final static String _semicolon = ";"; // in UTF-8
/** Read a mapping from the stream, as defined by the I2P data structure spec,
* and store it into a Properties object.
*
* A mapping is a set of key / value pairs. It starts with a 2 byte Integer (ala readLong(rawStream, 2))
* defining how many bytes make up the mapping. After that comes that many bytes making
* up a set of UTF-8 encoded characters. The characters are organized as key=value;.
* The key is a String (ala readString(rawStream)) unique as a key within the current
* mapping that does not include the UTF-8 characters '=' or ';'. After the key
* comes the literal UTF-8 character '='. After that comes a String (ala readString(rawStream))
* for the value. Finally after that comes the literal UTF-8 character ';'. This key=value;
* is repeated until there are no more bytes (not characters!) left as defined by the
* first two byte integer.
* @param rawStream stream to read the mapping from
* @throws DataFormatException if the format is invalid
* @throws IOException if there is a problem reading the data
* @return mapping
*/
public static Properties readProperties(InputStream rawStream) throws DataFormatException, IOException {
Properties props = new OrderedProperties();
long size = readLong(rawStream, 2);
byte data[] = new byte[(int)size];
int read = read(rawStream, data);
if (read != size)
throw new DataFormatException("Not enough data to read the properties");
ByteArrayInputStream in = new ByteArrayInputStream(data);
byte eqBuf[] = _equal.getBytes();
byte semiBuf[] = _semicolon.getBytes();
try {
while (in.available() > 0) {
String key = readString(in);
read = read(in, eqBuf);
if ((read != eqBuf.length) || (!eq(new String(eqBuf), _equal))) {
_log.debug("Failed eqtest [" + new String(eqBuf) + "]");
break;
}
String val = readString(in);
read = read(in, semiBuf);
if ((read != semiBuf.length) || (!eq(new String(semiBuf), _semicolon))) {
_log.debug("Failed semitest [" + new String(semiBuf) + "]");
break;
}
props.put(key, val);
}
} catch (IOException ioe) {
_log.warn("Error reading properties", ioe);
}
return props;
}
/**
* Write a mapping to the stream, as defined by the I2P data structure spec,
* and store it into a Properties object. See readProperties for the format.
*
* @param rawStream stream to write to
* @param props properties to write out
* @throws DataFormatException if there is not enough valid data to write out
* @throws IOException if there is an IO error writing out the data
*/
public static void writeProperties(OutputStream rawStream, Properties props) throws DataFormatException, IOException {
OrderedProperties p = new OrderedProperties();
if (props != null)
p.putAll(props);
ByteArrayOutputStream baos = new ByteArrayOutputStream(32);
for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = p.getProperty(key);
// now make sure they're in UTF-8
key = new String(key.getBytes(), "UTF-8");
val = new String(val.getBytes(), "UTF-8");
writeString(baos, key);
baos.write(_equal.getBytes());
writeString(baos, val);
baos.write(_semicolon.getBytes());
}
baos.close();
byte propBytes[] = baos.toByteArray();
writeLong(rawStream, 2, propBytes.length);
rawStream.write(propBytes);
}
/**
* Pretty print the mapping
*
*/
public static String toString(Properties options) {
StringBuffer buf = new StringBuffer();
if (options != null) {
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = options.getProperty(key);
buf.append("[").append(key).append("] = [").append(val).append("]");
}
} else {
buf.append("(null properties map)");
}
return buf.toString();
}
/**
* Pretty print the collection
*
*/
public static String toString(Collection col) {
StringBuffer buf = new StringBuffer();
if (col != null) {
for (Iterator iter = col.iterator(); iter.hasNext(); ) {
Object o = iter.next();
buf.append("[").append(o).append("]");
if (iter.hasNext())
buf.append(", ");
}
} else {
buf.append("null");
}
return buf.toString();
}
public static String toString(byte buf[]) {
if (buf == null) return "";
else return toString(buf, buf.length);
}
public static String toString(byte buf[], int len) {
if (buf == null) buf = "".getBytes();
StringBuffer out = new StringBuffer();
if (len > buf.length) {
for (int i = 0; i < len - buf.length; i++)
out.append("00");
}
for (int i = 0; i < buf.length && i < len; i++) {
StringBuffer temp = new StringBuffer(Integer.toHexString((int)buf[i]));
while (temp.length() < 2) {
temp.insert(0, '0');
}
temp = new StringBuffer(temp.substring(temp.length()-2));
out.append(temp.toString());
}
return out.toString();
}
public static String toDecimalString(byte buf[], int len) {
if (buf == null) buf = "".getBytes();
BigInteger val = new BigInteger(1, buf);
return val.toString(10);
}
public final static String toHexString(byte data[]) {
if ( (data == null) || (data.length <= 0) ) return "00";
BigInteger bi = new BigInteger(1, data);
return bi.toString(16);
}
public final static byte[] fromHexString(String val) {
BigInteger bv = new BigInteger(val, 16);
return bv.toByteArray();
}
/** Read the stream for an integer as defined by the I2P data structure specification.
* Integers are a fixed number of bytes (numBytes), stored as unsigned integers in network byte order.
* @param rawStream stream to read from
* @param numBytes number of bytes to read and format into a number
* @throws DataFormatException if the stream doesn't contain a validly formatted number of that many bytes
* @throws IOException if there is an IO error reading the number
* @return number
*/
public static long readLong(InputStream rawStream, int numBytes) throws DataFormatException, IOException {
if (numBytes > 8)
throw new DataFormatException("readLong doesn't currently support reading numbers > 8 bytes [as thats bigger than java's long]");
byte data[] = new byte[numBytes];
int num = read(rawStream, data);
if (num != numBytes)
throw new DataFormatException("Not enough bytes [" + num + "] as required for the field [" + numBytes + "]");
UnsignedInteger val = new UnsignedInteger(data);
return val.getLong();
}
/** Write an integer as defined by the I2P data structure specification to the stream.
* Integers are a fixed number of bytes (numBytes), stored as unsigned integers in network byte order.
* @param value value to write out
* @param rawStream stream to write to
* @param numBytes number of bytes to write the number into (padding as necessary)
* @throws DataFormatException if the stream doesn't contain a validly formatted number of that many bytes
* @throws IOException if there is an IO error writing to the stream
*/
public static void writeLong(OutputStream rawStream, int numBytes, long value) throws DataFormatException, IOException {
UnsignedInteger i = new UnsignedInteger(value);
rawStream.write(i.getBytes(numBytes));
}
/** Read in a date from the stream as specified by the I2P data structure spec.
* A date is an 8 byte unsigned integer in network byte order specifying the number of
* milliseconds since midnight on January 1, 1970 in the GMT timezone. If the number is
* 0, the date is undefined or null. (yes, this means you can't represent midnight on 1/1/1970)
* @param in stream to read from
* @throws DataFormatException if the stream doesn't contain a validly formatted date
* @throws IOException if there is an IO error reading the date
* @return date read, or null
*/
public static Date readDate(InputStream in) throws DataFormatException, IOException {
long date = readLong(in, 8);
if (date == 0L)
return null;
else
return new Date(date);
}
/** Write out a date to the stream as specified by the I2P data structure spec.
* @param out stream to write to
* @param date date to write (can be null)
* @throws DataFormatException if the date is not valid
* @throws IOException if there is an IO error writing the date
*/
public static void writeDate(OutputStream out, Date date) throws DataFormatException, IOException {
if (date == null)
writeLong(out, 8, 0L);
else
writeLong(out, 8, date.getTime());
}
/** Read in a string from the stream as specified by the I2P data structure spec.
* A string is 1 or more bytes where the first byte is the number of bytes (not characters!)
* in the string and the remaining 0-255 bytes are the non-null terminated UTF-8 encoded character array.
* @param in stream to read from
* @throws DataFormatException if the stream doesn't contain a validly formatted string
* @throws IOException if there is an IO error reading the string
* @return UTF-8 string
*/
public static String readString(InputStream in) throws DataFormatException, IOException {
int size = (int)readLong(in, 1);
byte raw[] = new byte[size];
int read = read(in, raw);
if (read != size)
throw new DataFormatException("Not enough bytes to read the string");
return new String(raw);
}
/** Write out a string to the stream as specified by the I2P data structure spec. Note that the max
* size for a string allowed by the spec is 255 bytes.
*
* @param out stream to write string
* @param string string to write out: null strings are perfectly valid, but strings of excess length will
* cause a DataFormatException to be thrown
* @throws DataFormatException if the string is not valid
* @throws IOException if there is an IO error writing the string
*/
public static void writeString(OutputStream out, String string) throws DataFormatException, IOException {
if (string == null) {
writeLong(out, 1, 0);
} else {
if (string.length() > 255)
throw new DataFormatException("The I2P data spec limits strings to 255 bytes or less, but this is " + string.length() + " [" + string + "]");
byte raw[] = string.getBytes();
writeLong(out, 1, raw.length);
out.write(raw);
}
}
/** Read in a boolean as specified by the I2P data structure spec.
* A boolean is 1 byte that is either 0 (false), 1 (true), or 2 (null)
* @param in stream to read from
* @throws DataFormatException if the boolean is not valid
* @throws IOException if there is an IO error reading the boolean
* @return boolean value, or null
*/
public static Boolean readBoolean(InputStream in) throws DataFormatException, IOException {
int val = (int)readLong(in, 1);
switch (val) {
case 0:
return Boolean.FALSE;
case 1:
return Boolean.TRUE;
case 2:
return null;
default:
throw new DataFormatException("Uhhh.. readBoolean read a value that isn't a known ternary val (0,1,2): " + val);
}
}
/** Write out a boolean as specified by the I2P data structure spec.
* A boolean is 1 byte that is either 0 (false), 1 (true), or 2 (null)
* @param out stream to write to
* @param bool boolean value, or null
* @throws DataFormatException if the boolean is not valid
* @throws IOException if there is an IO error writing the boolean
*/
public static void writeBoolean(OutputStream out, Boolean bool) throws DataFormatException, IOException {
if (bool == null)
writeLong(out, 1, 2);
else if (Boolean.TRUE.equals(bool))
writeLong(out, 1, 1);
else
writeLong(out, 1, 0);
}
//
// The following comparator helpers make it simpler to write consistently comparing
// functions for objects based on their value, not JVM memory address
//
/**
* Helper util to compare two objects, treating (null == null) as true, and
* (null == (!null)) as false.
*
*/
public final static boolean eq(Object lhs, Object rhs) {
try {
boolean eq = ( ( (lhs == null) && (rhs == null) ) ||
( (lhs != null) && (lhs.equals(rhs))) );
return eq;
} catch (ClassCastException cce) {
_log.warn("Error comparing [" + lhs + "] with [" + rhs + "]", cce);
return false;
}
}
/**
* Deep compare two collections, treating (null == null) as true,
* (null == (!null)) as false, and then comparing each element via eq(object, object).
* If the size of the collections are not equal, the comparison returns false.
* The collection order should be consistent, as this simply iterates across both and compares
* based on the value of each at each step along the way.
*
*/
public final static boolean eq(Collection lhs, Collection rhs) {
if ( (lhs == null) && (rhs == null) ) return true;
if ( (lhs == null) || (rhs == null) ) return false;
if (lhs.size() != rhs.size()) return false;
Iterator liter = lhs.iterator();
Iterator riter = rhs.iterator();
while ( (liter.hasNext()) && (riter.hasNext()) )
if (!(eq(liter.next(), riter.next())))
return false;
return true;
}
/**
* Compare the byte arrays byte by byte, treating (null == null) as
* true, (null == (!null)) as false, and unequal length arrays as false.
*
*/
public final static boolean eq(byte lhs[], byte rhs[]) {
boolean eq = ( ( (lhs == null) && (rhs == null) ) ||
( (lhs != null) && (rhs != null) && (Arrays.equals(lhs, rhs)) ) );
return eq;
}
/**
* Compare two integers, really just for consistency.
*/
public final static boolean eq(int lhs, int rhs) { return lhs == rhs; }
/**
* Compare two longs, really just for consistency.
*/
public final static boolean eq(long lhs, long rhs) { return lhs == rhs; }
/**
* Compare two bytes, really just for consistency.
*/
public final static boolean eq(byte lhs, byte rhs) { return lhs == rhs; }
public final static int compareTo(byte lhs[], byte rhs[]) {
if ( (rhs == null) && (lhs == null) ) return 0;
if (lhs == null) return -1;
if (rhs == null) return 1;
if (rhs.length < lhs.length) return 1;
if (rhs.length > lhs.length) return -1;
for (int i = 0; i < rhs.length; i++) {
if (rhs[i] > lhs[i]) return -1;
else if (rhs[i] < lhs[i]) return 1;
}
return 0;
}
public final static byte[] xor(byte lhs[], byte rhs[]) {
if ( (lhs == null) || (rhs == null) || (lhs.length != rhs.length) ) return null;
byte diff[] = new byte[lhs.length];
for (int i = 0; i < lhs.length; i++)
diff[i] = (byte)(lhs[i] ^ rhs[i]);
return diff;
}
//
// The following hashcode helpers make it simpler to write consistently hashing
// functions for objects based on their value, not JVM memory address
//
/**
* Calculate the hashcode of the object, using 0 for null
*
*/
public static int hashCode(Object obj) {
if (obj == null) return 0;
else return obj.hashCode();
}
/**
* Calculate the hashcode of the date, using 0 for null
*
*/
public static int hashCode(Date obj) {
if (obj == null) return 0;
else return (int)obj.getTime();
}
/**
* Calculate the hashcode of the byte array, using 0 for null
*
*/
public static int hashCode(byte b[]) {
int rv = 0;
if (b != null) {
for (int i = 0; i < b.length && i < 8; i++)
rv += b[i];
}
return rv;
}
/**
* Calculate the hashcode of the collection, using 0 for null
*
*/
public static int hashCode(Collection col) {
if (col == null) return 0;
int c = 0;
for (Iterator iter = col.iterator(); iter.hasNext(); )
c = 7*c + hashCode(iter.next());
return c;
}
public static int read(InputStream in, byte target[]) throws IOException {
int cur = 0;
while (cur < target.length) {
int numRead = in.read(target, cur, target.length - cur);
if (numRead == -1) {
if (cur == 0)
return -1; // throw new EOFException("EOF Encountered during reading");
else
return cur;
}
cur += numRead;
}
return cur;
}
public static List sortStructures(Collection dataStructures) {
if (dataStructures == null) return new ArrayList();
ArrayList rv = new ArrayList(dataStructures.size());
TreeMap tm = new TreeMap();
for (Iterator iter = dataStructures.iterator(); iter.hasNext(); ) {
DataStructure struct = (DataStructure)iter.next();
tm.put(struct.calculateHash().toString(), struct);
}
for (Iterator iter = tm.keySet().iterator(); iter.hasNext();) {
Object k = iter.next();
rv.add(tm.get(k));
}
return rv;
}
public static String formatDuration(long ms) {
if (ms < 30*1000) {
return ms + "ms";
} else if (ms < 5*60*1000) {
return (ms/1000) + "s";
} else if (ms < 90*60*1000) {
return (ms / (60*1000)) + "m";
} else if (ms < 3*24*60*60*1000) {
return (ms / (60*60*1000)) + "h";
} else {
return (ms / (24*60*60*1000)) + "d";
}
}
/** compress the data and return a new GZIP compressed array */
public static byte[] compress(byte orig[]) {
if ( (orig == null) || (orig.length <= 0) ) return orig;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(orig.length);
GZIPOutputStream out = new GZIPOutputStream(baos, orig.length);
out.write(orig);
out.finish();
out.flush();
byte rv[] = baos.toByteArray();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Compression of " + orig.length + " into " + rv.length + " (or " + 100.0d*(((double)orig.length) / ((double)rv.length)) + "% savings)");
return rv;
} catch (IOException ioe) {
_log.error("Error compressing?!", ioe);
return null;
}
}
/** decompress the GZIP compressed data (returning null on error) */
public static byte[] decompress(byte orig[]) {
if ( (orig == null) || (orig.length <= 0) ) return orig;
try {
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(orig), orig.length);
ByteArrayOutputStream baos = new ByteArrayOutputStream(orig.length * 2);
byte buf[] = new byte[4*1024];
while (true) {
int read = in.read(buf);
if (read == -1)
break;
baos.write(buf, 0, read);
}
byte rv[] = baos.toByteArray();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d*(((double)rv.length) / ((double)orig.length)) + "% savings)");
return rv;
} catch (IOException ioe) {
_log.error("Error decompressing?", ioe);
return null;
}
}
}

View File

@ -0,0 +1,64 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.Serializable;
/**
* Defines the class as a standard object with particular bit representation,
* exposing methods to read and write that representation.
*
* @author jrandom
*/
public interface DataStructure extends Serializable {
/**
* Load up the current object with data from the given stream. Data loaded
* this way must match the I2P data structure specification.
*
* @param in stream to read from
* @throws DataFormatException if the data is improperly formatted
* @throws IOException if there was a problem reading the stream
*/
public void readBytes(InputStream in) throws DataFormatException, IOException;
/**
* Write out the data structure to the stream, using the format defined in the
* I2P data structure specification.
*
* @param out stream to write to
* @throws DataFormatException if the data was incomplete or not yet ready to be written
* @throws IOException if there was a problem writing to the stream
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException;
/**
* render the structure into modified base 64 notation
* @return null on error
*/
public String toBase64();
/**
* Load the structure from the base 64 encoded data provided
*
*/
public void fromBase64(String data) throws DataFormatException;
public byte[] toByteArray();
public void fromByteArray(byte data[]) throws DataFormatException;
/**
* Calculate the SHA256 value of this object (useful for a few scenarios)
*
* @return SHA256 hash, or null if there were problems (data format or io errors)
*/
public Hash calculateHash();
}

View File

@ -0,0 +1,80 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.crypto.SHA256Generator;
import net.i2p.util.Log;
/**
* Base implementation of all data structures
*
* @author jrandom
*/
public abstract class DataStructureImpl implements DataStructure {
private final static Log _log = new Log(DataStructureImpl.class);
public String toBase64() {
byte data[] = toByteArray();
if (data == null) return null;
else return Base64.encode(data);
}
public void fromBase64(String data) throws DataFormatException {
if (data == null)
throw new DataFormatException("Null data passed in");
byte bytes[] = Base64.decode(data);
fromByteArray(bytes);
}
public Hash calculateHash() {
byte data[] = toByteArray();
if (data != null)
return SHA256Generator.getInstance().calculateHash(data);
return null;
}
public byte[] toByteArray() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
writeBytes(baos);
return baos.toByteArray();
} catch (IOException ioe) {
_log.error("Error writing out the byte array", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing out the byte array", dfe);
return null;
}
}
public void fromByteArray(byte data[]) throws DataFormatException {
if (data == null)
throw new DataFormatException("Null data passed in");
try {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
readBytes(bais);
} catch (IOException ioe) {
throw new DataFormatException("Error reading the byte array", ioe);
}
}
/**
* Repeated reads until the buffer is full or IOException is thrown
*
* @return number of bytes read (should always equal target.length)
*/
protected int read(InputStream in, byte target[]) throws IOException {
return DataHelper.read(in, target);
}
}

View File

@ -0,0 +1,102 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.util.Log;
/**
* Defines an end point in the I2P network. The Destination may move aroundn
* in the network, but messages sent to the Destination will find it
*
* @author jrandom
*/
public class Destination extends DataStructureImpl {
private final static Log _log = new Log(Destination.class);
private Certificate _certificate;
private SigningPublicKey _signingKey;
private PublicKey _publicKey;
private Hash __calculatedHash;
public Destination() {
setCertificate(null);
setSigningPublicKey(null);
setPublicKey(null);
__calculatedHash = null;
}
public Certificate getCertificate() { return _certificate; }
public void setCertificate(Certificate cert) {
_certificate = cert;
__calculatedHash = null;
}
public PublicKey getPublicKey() { return _publicKey; }
public void setPublicKey(PublicKey key) {
_publicKey = key;
__calculatedHash = null;
}
public SigningPublicKey getSigningPublicKey() { return _signingKey; }
public void setSigningPublicKey(SigningPublicKey key) {
_signingKey = key;
__calculatedHash = null;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_publicKey = new PublicKey();
_publicKey.readBytes(in);
_signingKey = new SigningPublicKey();
_signingKey.readBytes(in);
_certificate = new Certificate();
_certificate.readBytes(in);
__calculatedHash = null;
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_certificate == null) || (_publicKey == null) || (_signingKey == null) )
throw new DataFormatException("Not enough data to format the destination");
_publicKey.writeBytes(out);
_signingKey.writeBytes(out);
_certificate.writeBytes(out);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof Destination))
return false;
Destination dst = (Destination)object;
return DataHelper.eq(getCertificate(), dst.getCertificate()) &&
DataHelper.eq(getSigningPublicKey(), dst.getSigningPublicKey()) &&
DataHelper.eq(getPublicKey(), dst.getPublicKey());
}
public int hashCode() {
return DataHelper.hashCode(getCertificate()) +
DataHelper.hashCode(getSigningPublicKey()) +
DataHelper.hashCode(getPublicKey());
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[Destination: ");
buf.append("\n\tHash: ").append(calculateHash().toBase64());
buf.append("\n\tPublic Key: ").append(getPublicKey());
buf.append("\n\tSigning Public Key: ").append(getSigningPublicKey());
buf.append("\n\tCertificate: ").append(getCertificate());
buf.append("]");
return buf.toString();
}
public Hash calculateHash() {
if (__calculatedHash == null)
__calculatedHash = super.calculateHash();
return __calculatedHash;
}
}

View File

@ -0,0 +1,79 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the hash as defined by the I2P data structure spec.
* AA hash is the SHA-256 of some data, taking up 32 bytes.
*
* @author jrandom
*/
public class Hash extends DataStructureImpl {
private final static Log _log = new Log(Hash.class);
private byte[] _data;
private volatile String _stringified;
public final static int HASH_LENGTH = 32;
public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]);
public Hash() { setData(null); }
public Hash(byte data[]) { setData(data); }
public byte[] getData() { return _data; }
public void setData(byte[] data) {
_data = data;
_stringified = null;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[HASH_LENGTH];
_stringified = null;
int read = read(in, _data);
if (read != HASH_LENGTH)
throw new DataFormatException("Not enough bytes to read the hash");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the hash to write out");
if (_data.length != HASH_LENGTH)
throw new DataFormatException("Invalid size of data in the private key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof Hash))
return false;
return DataHelper.eq(_data, ((Hash)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
if (_stringified == null) {
StringBuffer buf = new StringBuffer(64);
buf.append("[Hash: ");
if (_data == null) {
buf.append("null hash");
} else {
buf.append(toBase64());
}
buf.append("]");
_stringified = buf.toString();
}
return _stringified;
}
}

View File

@ -0,0 +1,125 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import net.i2p.util.Log;
import net.i2p.util.Clock;
/**
* Defines the proof that a particular router / tunnel is allowed to receive
* messages for a particular Destination during some period of time.
*
* @author jrandom
*/
public class Lease extends DataStructureImpl {
private final static Log _log = new Log(Lease.class);
private RouterIdentity _routerIdentity;
private TunnelId _tunnelId;
private Date _end;
private int _numSuccess;
private int _numFailure;
public Lease() {
setRouterIdentity(null);
setTunnelId(null);
setEndDate(null);
setNumSuccess(0);
setNumFailure(0);
}
/** Retrieve the router at which the destination can be contacted
* @return identity of the router acting as a gateway
*/
public RouterIdentity getRouterIdentity() { return _routerIdentity; }
/** Configure the router at which the destination can be contacted
* @param ident router acting as the gateway
*/
public void setRouterIdentity(RouterIdentity ident) { _routerIdentity = ident; }
/** Tunnel on the gateway to communicate with
* @return tunnel ID
*/
public TunnelId getTunnelId() { return _tunnelId; }
/** Configure the tunnel on the gateway to communicate with
* @param id tunnel ID
*/
public void setTunnelId(TunnelId id) { _tunnelId = id; }
public Date getEndDate() { return _end; }
public void setEndDate(Date date) { _end = date; }
/**
* Transient attribute of the lease, used to note how many times messages sent
* to the destination through the current lease were successful.
*
*/
public int getNumSuccess() { return _numSuccess; }
public void setNumSuccess(int num) { _numSuccess = num; }
/**
* Transient attribute of the lease, used to note how many times messages sent
* to the destination through the current lease failed.
*
*/
public int getNumFailure() { return _numFailure; }
public void setNumFailure(int num) { _numFailure = num; }
/** has this lease already expired? */
public boolean isExpired() { return isExpired(0); }
/** has this lease already expired (giving allowing up the fudgeFactor milliseconds for clock skew)? */
public boolean isExpired(long fudgeFactor) {
if (_end == null) return true;
return _end.getTime() < Clock.getInstance().now() - fudgeFactor;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_routerIdentity = new RouterIdentity();
_routerIdentity.readBytes(in);
_tunnelId = new TunnelId();
_tunnelId.readBytes(in);
_end = DataHelper.readDate(in);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_routerIdentity == null) || (_tunnelId == null) )
throw new DataFormatException("Not enough data to write out a Lease");
_routerIdentity.writeBytes(out);
_tunnelId.writeBytes(out);
DataHelper.writeDate(out, _end);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof Lease) )
return false;
Lease lse = (Lease)object;
return DataHelper.eq(getEndDate(), lse.getEndDate()) &&
DataHelper.eq(getRouterIdentity(), lse.getRouterIdentity());
}
public int hashCode() {
return DataHelper.hashCode(getEndDate()) +
DataHelper.hashCode(getRouterIdentity()) +
DataHelper.hashCode(getTunnelId());
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[Lease: ");
buf.append("\n\tEnd Date: ").append(getEndDate());
buf.append("\n\tRouter Identity: ").append(getRouterIdentity());
buf.append("\n\tTunnelId: ").append(getTunnelId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,280 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.i2p.crypto.DSAEngine;
import net.i2p.util.Log;
import net.i2p.util.Clock;
/**
* Defines the set of leases a destination currently has.
*
* @author jrandom
*/
public class LeaseSet extends DataStructureImpl {
private final static Log _log = new Log(LeaseSet.class);
private Destination _destination;
private PublicKey _encryptionKey;
private SigningPublicKey _signingKey;
private List _leases;
private Signature _signature;
private volatile Hash _currentRoutingKey;
private volatile byte[] _routingKeyGenMod;
/** um, no lease can last more than a year. */
private final static long MAX_FUTURE_EXPIRATION = 365*24*60*60*1000L;
public LeaseSet() {
setDestination(null);
setEncryptionKey(null);
setSigningKey(null);
setSignature(null);
setRoutingKey(null);
_leases = new ArrayList();
_routingKeyGenMod = null;
}
public Destination getDestination() { return _destination; }
public void setDestination(Destination dest) { _destination = dest; }
public PublicKey getEncryptionKey() { return _encryptionKey; }
public void setEncryptionKey(PublicKey encryptionKey) { _encryptionKey = encryptionKey; }
public SigningPublicKey getSigningKey() { return _signingKey; }
public void setSigningKey(SigningPublicKey key) { _signingKey = key; }
public void addLease(Lease lease) { _leases.add(lease); }
public void removeLease(Lease lease) { _leases.remove(lease); }
public int getLeaseCount() { return _leases.size(); }
public Lease getLease(int index) { return (Lease)_leases.get(index); }
public Signature getSignature() { return _signature; }
public void setSignature(Signature sig) { _signature = sig; }
/**
* Get the routing key for the structure using the current modifier in the RoutingKeyGenerator.
* This only calculates a new one when necessary though (if the generator's key modifier changes)
*
*/
public Hash getRoutingKey() {
RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance();
if ( (gen.getModData() == null) || (_routingKeyGenMod == null) || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod)) ) {
setRoutingKey(gen.getRoutingKey(getDestination().calculateHash()));
_routingKeyGenMod = gen.getModData();
}
return _currentRoutingKey;
}
public void setRoutingKey(Hash key) { _currentRoutingKey = key; }
public boolean validateRoutingKey() {
Hash destKey = getDestination().calculateHash();
Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(destKey);
if (rk.equals(getRoutingKey()))
return true;
else
return false;
}
/**
* Retrieve the end date of the earliest lease include in this leaseSet.
* This is the date that should be used in comparisons for leaseSet age - to
* determine which LeaseSet was published more recently (later earliestLeaseSetDate
* means it was published later)
*
* @return earliest end date of any lease in the set, or -1 if there are no leases
*/
public long getEarliestLeaseDate() {
long when = -1;
for (int i = 0; i < getLeaseCount(); i++) {
Lease lse = (Lease)getLease(i);
if ( (lse != null) && (lse.getEndDate() != null) ) {
if ( (when <= 0) || (lse.getEndDate().getTime() < when) )
when = lse.getEndDate().getTime();
}
}
return when;
}
/**
* Sign the structure using the supplied signing key
*
*/
public void sign(SigningPrivateKey key) throws DataFormatException {
byte[] bytes = getBytes();
if (bytes == null) throw new DataFormatException("Not enough data to sign");
// now sign with the key
Signature sig = DSAEngine.getInstance().sign(bytes, key);
setSignature(sig);
}
/**
* Verify that the signature matches the lease set's destination's signing public key.
*
* @return true only if the signature matches
*/
public boolean verifySignature() {
if (getSignature() == null) return false;
if (getDestination() == null) return false;
byte data[] = getBytes();
if (data == null) return false;
boolean signedByDest = DSAEngine.getInstance().verifySignature(getSignature(), data, getDestination().getSigningPublicKey());
boolean signedByRevoker = false;
if (!signedByDest) {
signedByRevoker = DSAEngine.getInstance().verifySignature(getSignature(), data, _signingKey);
}
return signedByDest || signedByRevoker;
}
/**
* Verify that the signature matches the lease set's destination's signing public key.
*
* @return true only if the signature matches
*/
public boolean verifySignature(SigningPublicKey signingKey) {
if (getSignature() == null) return false;
if (getDestination() == null) return false;
byte data[] = getBytes();
if (data == null) return false;
boolean signedByDest = DSAEngine.getInstance().verifySignature(getSignature(), data, getDestination().getSigningPublicKey());
boolean signedByRevoker = false;
if (!signedByDest) {
signedByRevoker = DSAEngine.getInstance().verifySignature(getSignature(), data, signingKey);
}
return signedByDest || signedByRevoker;
}
/**
* Determine whether there are currently valid leases, at least within a given
* fudge factor
*
* @param fudge milliseconds fudge factor to allow between the current time
* @return true if there are current leases, false otherwise
*/
public boolean isCurrent(long fudge) {
long now = Clock.getInstance().now();
long insane = now + MAX_FUTURE_EXPIRATION;
int cnt = getLeaseCount();
for (int i = 0; i < cnt; i++) {
Lease l = getLease(i);
if (l.getEndDate().getTime() > insane) {
_log.warn("LeaseSet" + calculateHash() + " expires an insane amount in the future - skip it: " + l);
return false;
}
// if it hasn't finished, we're current
if (l.getEndDate().getTime() > now) {
_log.debug("LeaseSet " + calculateHash() + " isn't exired: " + l);
return true;
} else if (l.getEndDate().getTime() > now - fudge) {
_log.debug("LeaseSet " + calculateHash() + " isn't quite expired, but its within the fudge factor so we'll let it slide: " + l);
return true;
}
}
return false;
}
private byte[] getBytes() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
if ( (_destination == null) || (_encryptionKey == null) || (_signingKey == null) ||
(_leases == null) )
return null;
_destination.writeBytes(out);
_encryptionKey.writeBytes(out);
_signingKey.writeBytes(out);
DataHelper.writeLong(out, 1, _leases.size());
//DataHelper.writeLong(out, 4, _version);
for (Iterator iter = _leases.iterator(); iter.hasNext();) {
Lease lease = (Lease)iter.next();
lease.writeBytes(out);
}
} catch (IOException ioe) {
return null;
} catch (DataFormatException dfe) {
return null;
}
return out.toByteArray();
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_destination = new Destination();
_destination.readBytes(in);
_encryptionKey = new PublicKey();
_encryptionKey.readBytes(in);
_signingKey = new SigningPublicKey();
_signingKey.readBytes(in);
int numLeases = (int)DataHelper.readLong(in, 1);
//_version = DataHelper.readLong(in, 4);
_leases.clear();
for (int i = 0; i < numLeases; i++) {
Lease lease = new Lease();
lease.readBytes(in);
_leases.add(lease);
}
_signature = new Signature();
_signature.readBytes(in);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_destination == null) || (_encryptionKey == null) || (_signingKey == null) ||
(_leases == null) || (_signature == null) )
throw new DataFormatException("Not enough data to write out a LeaseSet");
_destination.writeBytes(out);
_encryptionKey.writeBytes(out);
_signingKey.writeBytes(out);
DataHelper.writeLong(out, 1, _leases.size());
//DataHelper.writeLong(out, 4, _version);
for (Iterator iter = _leases.iterator(); iter.hasNext();) {
Lease lease = (Lease)iter.next();
lease.writeBytes(out);
}
_signature.writeBytes(out);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof LeaseSet) )
return false;
LeaseSet ls = (LeaseSet)object;
return DataHelper.eq(getEncryptionKey(), ls.getEncryptionKey()) &&
//DataHelper.eq(getVersion(), ls.getVersion()) &&
DataHelper.eq(_leases, ls._leases) &&
DataHelper.eq(getSignature(), ls.getSignature()) &&
DataHelper.eq(getSigningKey(), ls.getSigningKey()) &&
DataHelper.eq(getDestination(), ls.getDestination());
}
public int hashCode() {
return DataHelper.hashCode(getEncryptionKey()) +
//(int)_version +
DataHelper.hashCode(_leases) +
DataHelper.hashCode(getSignature()) +
DataHelper.hashCode(getSigningKey()) +
DataHelper.hashCode(getDestination());
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[LeaseSet: ");
buf.append("\n\tDestination: ").append(getDestination());
buf.append("\n\tEncryptionKey: ").append(getEncryptionKey());
buf.append("\n\tSigningKey: ").append(getSigningKey());
//buf.append("\n\tVersion: ").append(getVersion());
buf.append("\n\tSignature: ").append(getSignature());
buf.append("\n\tLeases: #").append(getLeaseCount());
for (int i = 0; i < getLeaseCount(); i++)
buf.append("\n\t\tLease (").append(i).append("): ").append(getLease(i));
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,100 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.util.Log;
/**
* Defines the actual payload of a message being delivered, including the
* standard encryption wrapping, as defined by the I2P data structure spec.<p />
*
* @author jrandom
*/
public class Payload extends DataStructureImpl {
private final static Log _log = new Log(Payload.class);
private byte[] _encryptedData;
private byte[] _unencryptedData;
public Payload() {
setUnencryptedData(null);
setEncryptedData(null);
}
/**
* Retrieve the unencrypted body of the message.
*
* @return body of the message, or null if the message has either not been
* decrypted yet or if the hash is not correct
*/
public byte[] getUnencryptedData() {
return _unencryptedData;
}
/**
* Populate the message body with data. This does not automatically encrypt
* yet.
*
*/
public void setUnencryptedData(byte[] data) { _unencryptedData = data; }
public byte[] getEncryptedData() { return _encryptedData; }
public void setEncryptedData(byte[] data) { _encryptedData = data; }
public int getSize() {
if (_unencryptedData != null)
return _unencryptedData.length;
else if (_encryptedData != null)
return _encryptedData.length;
else
return 0;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
int size = (int)DataHelper.readLong(in, 4);
_encryptedData = new byte[size];
int read = read(in, _encryptedData);
if (read != size)
throw new DataFormatException("Incorrect number of bytes read in the payload structure");
_log.debug("read payload: " + read + " bytes");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_encryptedData == null)
throw new DataFormatException("Not yet encrypted. Please set the encrypted data");
DataHelper.writeLong(out, 4, _encryptedData.length);
out.write(_encryptedData);
_log.debug("wrote payload: " + _encryptedData.length);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof Payload) )
return false;
Payload p = (Payload)object;
return DataHelper.eq(getUnencryptedData(), p.getUnencryptedData()) &&
DataHelper.eq(getEncryptedData(), p.getEncryptedData());
}
public int hashCode() {
return DataHelper.hashCode(getUnencryptedData());
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[Payload: ");
if (getUnencryptedData() != null)
buf.append("\n\tData: ").append(DataHelper.toString(getUnencryptedData(), 16));
else
buf.append("\n\tData: *encrypted* = [").append(DataHelper.toString(getEncryptedData(), 16)).append("]");
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the PrivateKey as defined by the I2P data structure spec.
* A private key is 256byte Integer. The private key represents only the
* exponent, not the primes, which are constant and defined in the crypto spec.
*
* @author jrandom
*/
public class PrivateKey extends DataStructureImpl {
private final static Log _log = new Log(PrivateKey.class);
private byte[] _data;
public final static int KEYSIZE_BYTES = 256;
public PrivateKey() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[KEYSIZE_BYTES];
int read = read(in, _data);
if (read != KEYSIZE_BYTES)
throw new DataFormatException("Not enough bytes to read the private key");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the private key to write out");
if (_data.length != KEYSIZE_BYTES)
throw new DataFormatException("Invalid size of data in the private key [" + _data.length + "]");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof PrivateKey))
return false;
return DataHelper.eq(_data, ((PrivateKey)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[PrivateKey: ");
if (_data == null) {
buf.append("null key");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,75 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the PublicKey as defined by the I2P data structure spec.
* A public key is 256byte Integer. The public key represents only the
* exponent, not the primes, which are constant and defined in the crypto spec.
*
* @author jrandom
*/
public class PublicKey extends DataStructureImpl {
private final static Log _log = new Log(PublicKey.class);
private byte[] _data;
public final static int KEYSIZE_BYTES = 256;
public PublicKey() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[KEYSIZE_BYTES];
int read = read(in, _data);
if (read != KEYSIZE_BYTES)
throw new DataFormatException("Not enough bytes to read the public key");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the public key to write out");
if (_data.length != KEYSIZE_BYTES)
throw new DataFormatException("Invalid size of data in the public key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof PublicKey))
return false;
return DataHelper.eq(_data, ((PublicKey)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[PublicKey: ");
if (_data == null) {
buf.append("null key");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,133 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Iterator;
import java.util.Date;
import net.i2p.util.Log;
/**
* Defines a method of communicating with a router
*
* @author jrandom
*/
public class RouterAddress extends DataStructureImpl {
private final static Log _log = new Log(RouterAddress.class);
private int _cost;
private Date _expiration;
private String _transportStyle;
private Properties _options;
public RouterAddress() {
setCost(-1);
setExpiration(null);
setTransportStyle(null);
setOptions(null);
}
/**
* Retrieve the weighted cost of this address, relative to other methods of
* contacting this router. 0 means free and 255 means really expensive.
* No value above 255 is allowed.
*
*/
public int getCost() { return _cost; }
/**
* Configure the weighted cost of using the address.
* No value above 255 is allowed.
*
*/
public void setCost(int cost) { _cost = cost; }
/**
* Retrieve the date after which the address should not be used. If this
* is null, then the address never expires.
*
*/
public Date getExpiration() { return _expiration; }
/**
* Configure the expiration date of the address (null for no expiration)
*
*/
public void setExpiration(Date expiration) { _expiration = expiration; }
/**
* Retrieve the type of transport that must be used to communicate on this address.
*
*/
public String getTransportStyle() { return _transportStyle; }
/**
* Configure the type of transport that must be used to communicate on this address
*
*/
public void setTransportStyle(String transportStyle) { _transportStyle = transportStyle; }
/**
* Retrieve the transport specific options necessary for communication
*
*/
public Properties getOptions() { return _options; }
/**
* Specify the transport specific options necessary for communication
*
*/
public void setOptions(Properties options) { _options = options; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_cost = (int)DataHelper.readLong(in, 1);
_expiration = DataHelper.readDate(in);
_transportStyle = DataHelper.readString(in);
_options = DataHelper.readProperties(in);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_cost < 0) || (_transportStyle == null) || (_options == null) )
throw new DataFormatException("Not enough data to write a router address");
DataHelper.writeLong(out, 1, _cost);
DataHelper.writeDate(out, _expiration);
DataHelper.writeString(out, _transportStyle);
DataHelper.writeProperties(out, _options);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof RouterAddress) )
return false;
RouterAddress addr = (RouterAddress)object;
return DataHelper.eq(getCost(), addr.getCost()) &&
DataHelper.eq(getExpiration(), addr.getExpiration()) &&
DataHelper.eq(getOptions(), addr.getOptions()) &&
DataHelper.eq(getTransportStyle(), addr.getTransportStyle());
}
public int hashCode() {
return getCost() +
DataHelper.hashCode(getTransportStyle()) +
DataHelper.hashCode(getExpiration()) +
DataHelper.hashCode(getOptions());
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[RouterAddress: ");
buf.append("\n\tTransportStyle: ").append(getTransportStyle());
buf.append("\n\tCost: ").append(getCost());
buf.append("\n\tExpiration: ").append(getExpiration());
buf.append("\n\tOptions: #: ").append(getOptions().size());
for (Iterator iter = getOptions().keySet().iterator(); iter.hasNext();) {
String key = (String)iter.next();
String val = getOptions().getProperty(key);
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,117 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.i2p.crypto.SHA256Generator;
import net.i2p.util.Log;
/**
* Defines the unique identifier of a router, including any certificate or
* public key.
*
* @author jrandom
*/
public class RouterIdentity extends DataStructureImpl {
private final static Log _log = new Log(RouterIdentity.class);
private Certificate _certificate;
private SigningPublicKey _signingKey;
private PublicKey _publicKey;
private Hash __calculatedHash;
public RouterIdentity() {
setCertificate(null);
setSigningPublicKey(null);
setPublicKey(null);
__calculatedHash = null;
}
public Certificate getCertificate() { return _certificate; }
public void setCertificate(Certificate cert) {
_certificate = cert;
__calculatedHash = null;
}
public PublicKey getPublicKey() { return _publicKey; }
public void setPublicKey(PublicKey key) {
_publicKey = key;
__calculatedHash = null;
}
public SigningPublicKey getSigningPublicKey() { return _signingKey; }
public void setSigningPublicKey(SigningPublicKey key) {
_signingKey = key;
__calculatedHash = null;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_publicKey = new PublicKey();
_publicKey.readBytes(in);
_signingKey = new SigningPublicKey();
_signingKey.readBytes(in);
_certificate = new Certificate();
_certificate.readBytes(in);
__calculatedHash = null;
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_certificate == null) || (_publicKey == null) || (_signingKey == null) )
throw new DataFormatException("Not enough data to format the destination");
_publicKey.writeBytes(out);
_signingKey.writeBytes(out);
_certificate.writeBytes(out);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof RouterIdentity) )
return false;
RouterIdentity ident = (RouterIdentity)object;
return DataHelper.eq(getCertificate(), ident.getCertificate()) &&
DataHelper.eq(getSigningPublicKey(), ident.getSigningPublicKey()) &&
DataHelper.eq(getPublicKey(), ident.getPublicKey());
}
public int hashCode() {
return DataHelper.hashCode(getCertificate()) +
DataHelper.hashCode(getSigningPublicKey()) +
DataHelper.hashCode(getPublicKey());
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[RouterIdentity: ");
buf.append("\n\tHash: ").append(getHash().toBase64());
buf.append("\n\tCertificate: ").append(getCertificate());
buf.append("\n\tPublicKey: ").append(getPublicKey());
buf.append("\n\tSigningPublicKey: ").append(getSigningPublicKey());
buf.append("]");
return buf.toString();
}
public Hash calculateHash() { return getHash(); }
public Hash getHash() {
if (__calculatedHash != null) {
//_log.info("Returning cached ident hash");
return __calculatedHash;
}
byte identBytes[] = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeBytes(baos);
identBytes = baos.toByteArray();
} catch (Throwable t) {
_log.error("Error writing out hash");
return null;
}
__calculatedHash = SHA256Generator.getInstance().calculateHash(identBytes);
return __calculatedHash;
}
}

View File

@ -0,0 +1,432 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.List;
import java.util.Date;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.Clock;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SHA256Generator;
/**
* Defines the data that a router either publishes to the global routing table or
* provides to trusted peers.
*
* @author jrandom
*/
public class RouterInfo extends DataStructureImpl {
private final static Log _log = new Log(RouterInfo.class);
private RouterIdentity _identity;
private volatile long _published;
private Set _addresses;
private Set _peers;
private Properties _options;
private volatile Signature _signature;
private volatile Hash _currentRoutingKey;
private volatile byte _routingKeyGenMod[];
private volatile boolean _validated;
private volatile boolean _isValid;
private volatile String _stringified;
private volatile byte _byteified[];
public RouterInfo() {
setIdentity(null);
setPublished(0);
_addresses = new HashSet();
_peers = new HashSet();
_options = new OrderedProperties();
setSignature(null);
_validated = false;
_isValid = false;
_currentRoutingKey = null;
_stringified = null;
_byteified = null;
}
public RouterInfo(RouterInfo old) {
this();
setIdentity(old.getIdentity());
setPublished(old.getPublished());
setAddresses(old.getAddresses());
setPeers(old.getPeers());
setOptions(old.getOptions());
setSignature(old.getSignature());
}
private void resetCache() {
_stringified = null;
_byteified = null;
}
/**
* Retrieve the identity of the router represented
*
*/
public RouterIdentity getIdentity() { return _identity; }
/**
* Configure the identity of the router represented
*
*/
public void setIdentity(RouterIdentity ident) {
_identity = ident;
resetCache();
}
/**
* Retrieve the approximate date on which the info was published
* (essentially a version number for the routerInfo structure, except that
* it also contains freshness information - whether or not the router is
* currently publishing its information). This should be used to help expire
* old routerInfo structures
*
*/
public long getPublished() { return _published; }
/**
* Date on which it was published, in milliseconds since Midnight GMT on Jan 01, 1970
*
*/
public void setPublished(long published) { _published = published; }
/**
* Retrieve the set of RouterAddress structures at which this
* router can be contacted.
*
*/
public Set getAddresses() {
synchronized (_addresses) {
return new HashSet(_addresses);
}
}
/**
* Specify a set of RouterAddress structures at which this router
* can be contacted.
*
*/
public void setAddresses(Set addresses) {
synchronized (_addresses) {
_addresses.clear();
if (addresses != null)
_addresses.addAll(addresses);
}
resetCache();
}
/**
* Retrieve a set of SHA-256 hashes of RouterIdentities from rotuers
* this router can be reached through.
*
*/
public Set getPeers() { return _peers; }
/**
* Specify a set of SHA-256 hashes of RouterIdentities from rotuers
* this router can be reached through.
*
*/
public void setPeers(Set peers) {
synchronized (_peers) {
_peers.clear();
if (peers != null)
_peers.addAll(peers);
}
resetCache();
}
/**
* Retrieve a set of options or statistics that the router can expose
*
*/
public Properties getOptions() {
if (_options == null) return new Properties();
synchronized (_options) {
return (Properties)_options.clone();
}
}
/**
* Configure a set of options or statistics that the router can expose
*
*/
public void setOptions(Properties options) {
synchronized (_options) {
_options.clear();
if (options != null) {
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
if (name == null) continue;
String val = options.getProperty(name);
if (val == null) continue;
_options.setProperty(name, val);
}
}
}
resetCache();
}
/**
* Retrieve the proof that the identity stands behind the info here
*
*/
public Signature getSignature() { return _signature; }
/**
* Configure the proof that the entity stands behind the info here
*
*/
public void setSignature(Signature signature) {
_signature = signature;
resetCache();
}
/**
* Sign the structure using the supplied signing key
*
*/
public synchronized void sign(SigningPrivateKey key) throws DataFormatException {
byte[] bytes = getBytes();
if (bytes == null) throw new DataFormatException("Not enough data to sign");
// now sign with the key
Signature sig = DSAEngine.getInstance().sign(bytes, key);
setSignature(sig);
//_log.debug("Signed " + SHA256Generator.getInstance().calculateHash(bytes).toBase64() + " with " + key);
//_log.debug("verify ok? " + DSAEngine.getInstance().verifySignature(sig, bytes, getIdentity().getSigningPublicKey()));
//_log.debug("Signed data: \n" + Base64.encode(bytes));
//_log.debug("Signature: " + getSignature());
resetCache();
}
private byte[] getBytes() throws DataFormatException {
if (_byteified != null) return _byteified;
if (_identity == null) throw new IllegalStateException("Router identity isn't set? wtf!");
if (_addresses == null) throw new IllegalStateException("Router addressess isn't set? wtf!");
if (_peers == null) throw new IllegalStateException("Router peers isn't set? wtf!");
if (_options == null) throw new IllegalStateException("Router options isn't set? wtf!");
long before = Clock.getInstance().now();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
_identity.writeBytes(out);
DataHelper.writeDate(out, new Date(_published));
DataHelper.writeLong(out, 1, _addresses.size());
List addresses = DataHelper.sortStructures(_addresses);
for (Iterator iter = addresses.iterator(); iter.hasNext(); ) {
RouterAddress addr = (RouterAddress)iter.next();
addr.writeBytes(out);
}
DataHelper.writeLong(out, 1, _peers.size());
List peers = DataHelper.sortStructures(_peers);
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
Hash peerHash = (Hash)iter.next();
peerHash.writeBytes(out);
}
DataHelper.writeProperties(out, _options);
} catch (IOException ioe) {
throw new DataFormatException("IO Error getting bytes", ioe);
}
byte data[] = out.toByteArray();
long after = Clock.getInstance().now();
_log.debug("getBytes() took " + (after-before) + "ms");
_byteified = data;
return data;
}
/**
* Determine whether this router info is authorized with a valid signature
*
*/
public synchronized boolean isValid() {
if (!_validated)
doValidate();
return _isValid;
}
/**
* Get the routing key for the structure using the current modifier in the RoutingKeyGenerator.
* This only calculates a new one when necessary though (if the generator's key modifier changes)
*
*/
public synchronized Hash getRoutingKey() {
RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance();
if ( (gen.getModData() == null) || (_routingKeyGenMod == null) || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod)) ) {
setRoutingKey(gen.getRoutingKey(getIdentity().getHash()));
_routingKeyGenMod = gen.getModData();
}
return _currentRoutingKey;
}
public void setRoutingKey(Hash key) { _currentRoutingKey = key; }
public boolean validateRoutingKey() {
Hash identKey = getIdentity().getHash();
Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(identKey);
if (rk.equals(getRoutingKey()))
return true;
else
return false;
}
/**
* Determine whether the router was published recently (within the given age milliseconds).
* The age should be large enough to take into consideration any clock fudge factor, so
* values such as 1 or 2 hours are probably reasonable.
*
* @param maxAgeMs milliseconds between the current time and publish date to check
* @return true if it was published recently, false otherwise
*/
public boolean isCurrent(long maxAgeMs) {
long earliestExpire = Clock.getInstance().now() - maxAgeMs;
if (getPublished() < earliestExpire) {
return false;
} else {
return true;
}
}
/**
* Actually validate the signature
*/
private synchronized void doValidate() {
_validated = true;
if (getSignature() == null) {
_log.error("Signature is null");
_isValid = false;
return;
}
byte data[] = null;
try {
data = getBytes();
} catch (DataFormatException dfe) {
_log.error("Error validating", dfe);
_isValid = false;
return;
}
if (data == null) {
_log.error("Data could not be loaded");
_isValid = false;
return;
}
_isValid = DSAEngine.getInstance().verifySignature(_signature, data, _identity.getSigningPublicKey());
if (!_isValid) {
_log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64() + "] w/ signing key: " + _identity.getSigningPublicKey(), new Exception("Signature failed"));
_log.debug("Failed data: \n" + Base64.encode(data));
_log.debug("Signature: " + getSignature());
}
}
public synchronized void readBytes(InputStream in) throws DataFormatException, IOException {
_identity = new RouterIdentity();
_identity.readBytes(in);
_published = DataHelper.readDate(in).getTime();
int numAddresses = (int)DataHelper.readLong(in, 1);
for (int i = 0; i < numAddresses; i++) {
RouterAddress address = new RouterAddress();
address.readBytes(in);
_addresses.add(address);
}
int numPeers = (int)DataHelper.readLong(in, 1);
for (int i = 0; i < numPeers; i++) {
Hash peerIdentityHash = new Hash();
peerIdentityHash.readBytes(in);
_peers.add(peerIdentityHash);
}
_options = DataHelper.readProperties(in);
_signature = new Signature();
_signature.readBytes(in);
resetCache();
//_log.debug("Read routerInfo: " + toString());
}
public synchronized void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_identity == null)
throw new DataFormatException("Missing identity");
if (_published <= 0)
throw new DataFormatException("Invalid published date: " + _published);
if (_signature == null)
throw new DataFormatException("Signature is null");
//if (!isValid())
// throw new DataFormatException("Data is not valid");
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
_identity.writeBytes(baos);
DataHelper.writeDate(baos, new Date(_published));
DataHelper.writeLong(baos, 1, _addresses.size());
for (Iterator iter = _addresses.iterator(); iter.hasNext(); ) {
RouterAddress addr = (RouterAddress)iter.next();
addr.writeBytes(baos);
}
DataHelper.writeLong(baos, 1, _peers.size());
for (Iterator iter = _peers.iterator(); iter.hasNext(); ) {
Hash peerHash = (Hash)iter.next();
peerHash.writeBytes(baos);
}
DataHelper.writeProperties(baos, _options);
_signature.writeBytes(baos);
byte data[] = baos.toByteArray();
//_log.debug("Writing routerInfo [len=" + data.length + "]: " + toString());
out.write(data);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof RouterInfo) )
return false;
RouterInfo info = (RouterInfo)object;
return DataHelper.eq(getAddresses(), info.getAddresses()) &&
DataHelper.eq(getIdentity(), info.getIdentity()) &&
DataHelper.eq(getOptions(), info.getOptions()) &&
DataHelper.eq(getPeers(), info.getPeers()) &&
DataHelper.eq(getSignature(), info.getSignature()) &&
DataHelper.eq(getPublished(), info.getPublished());
}
public int hashCode() {
return DataHelper.hashCode(getAddresses()) +
DataHelper.hashCode(getIdentity()) +
DataHelper.hashCode(getOptions()) +
DataHelper.hashCode(getPeers()) +
DataHelper.hashCode(getSignature()) +
(int)getPublished();
}
public String toString() {
if (_stringified != null)
return _stringified;
StringBuffer buf = new StringBuffer(128);
buf.append("[RouterInfo: ");
buf.append("\n\tIdentity: ").append(getIdentity());
buf.append("\n\tSignature: ").append(getSignature());
buf.append("\n\tPublished on: ").append(new Date(getPublished()));
buf.append("\n\tAddresses: #: ").append(getAddresses().size());
for (Iterator iter = getAddresses().iterator(); iter.hasNext();) {
RouterAddress addr = (RouterAddress)iter.next();
buf.append("\n\t\tAddress: ").append(addr);
}
buf.append("\n\tPeers: #: ").append(getPeers().size());
for (Iterator iter = getPeers().iterator(); iter.hasNext();) {
Hash hash = (Hash)iter.next();
buf.append("\n\t\tPeer hash: ").append(hash);
}
Properties options = getOptions();
buf.append("\n\tOptions: #: ").append(options.size());
for (Iterator iter = options.keySet().iterator(); iter.hasNext();) {
String key = (String)iter.next();
String val = options.getProperty(key);
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
buf.append("]");
_stringified = buf.toString();
return _stringified;
}
}

View File

@ -0,0 +1,112 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.crypto.SHA256Generator;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.util.Clock;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.Date;
import java.text.SimpleDateFormat;
/**
* Component to manage the munging of hashes into routing keys - given a hash,
* perform some consistent transformation against it and return the result.
* This transformation is fed by the current "mod data".
*
* Right now the mod data is the current date (GMT) as a string: "yyyyMMdd",
* and the transformation takes the original hash, appends the bytes of that mod data,
* then returns the SHA256 of that concatenation.
*
* Do we want this to simply do the XOR of the SHA256 of the current mod data and
* the key? does that provide the randomization we need? It'd save an SHA256 op.
* Bah, too much effort to think about for so little gain. Other algorithms may come
* into play layer on about making periodic updates to the routing key for data elements
* to mess with Sybil. This may be good enough though.
*
* Also - the method generateDateBasedModData() should be called after midnight GMT
* once per day to generate the correct routing keys!
*
*/
public class RoutingKeyGenerator {
private final static RoutingKeyGenerator _instance = new RoutingKeyGenerator();
public static RoutingKeyGenerator getInstance() { return _instance; }
private final static Log _log = new Log(RoutingKeyGenerator.class);
private byte _currentModData[];
private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
private final static SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd");
public byte[] getModData() { return _currentModData; }
public void setModData(byte modData[]) {
_currentModData = modData;
}
/**
* Update the current modifier data with some bytes derived from the current
* date (yyyyMMdd in GMT)
*
*/
public void generateDateBasedModData() {
Date today = null;
synchronized (_cal) {
_cal.setTime(new Date(Clock.getInstance().now()));
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MILLISECOND, 0);
today = _cal.getTime();
}
byte mod[] = null;
synchronized (_fmt) {
mod = _fmt.format(today).getBytes();
}
_log.info("Routing modifier generated: " + new String(mod));
setModData(mod);
}
/**
* Generate a modified (yet consistent) hash from the origKey by generating the
* SHA256 of the targetKey with the current modData appended to it, *then*
*
* This makes Sybil's job a lot harder, as she needs to essentially take over the
* whole keyspace.
*
* @throws IllegalArgumentException if origKey is null
*/
public Hash getRoutingKey(Hash origKey) {
if (origKey == null)
throw new IllegalArgumentException("Original key is null");
if (_currentModData == null) generateDateBasedModData();
byte modVal[] = new byte[Hash.HASH_LENGTH+_currentModData.length];
System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH);
System.arraycopy(_currentModData, 0, modVal, Hash.HASH_LENGTH, _currentModData.length);
return SHA256Generator.getInstance().calculateHash(modVal);
}
public static void main(String args[]) {
Hash k1 = new Hash();
byte k1d[] = new byte[Hash.HASH_LENGTH];
RandomSource.getInstance().nextBytes(k1d);
k1.setData(k1d);
for (int i = 0; i < 10; i++) {
System.out.println("K1: " + k1);
Hash k1m = RoutingKeyGenerator.getInstance().getRoutingKey(k1);
System.out.println("MOD: " + new String(RoutingKeyGenerator.getInstance().getModData()));
System.out.println("K1M: " + k1m);
}
try { Thread.sleep(2000); } catch (Throwable t) {}
}
}

View File

@ -0,0 +1,75 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the SessionKey as defined by the I2P data structure spec.
* A session key is 32byte Integer.
*
* @author jrandom
*/
public class SessionKey extends DataStructureImpl {
private final static Log _log = new Log(SessionKey.class);
private byte[] _data;
public final static int KEYSIZE_BYTES = 32;
public SessionKey() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[KEYSIZE_BYTES];
int read = read(in, _data);
if (read != KEYSIZE_BYTES)
throw new DataFormatException("Not enough bytes to read the session key");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the session key to write out");
if (_data.length != KEYSIZE_BYTES)
throw new DataFormatException("Invalid size of data in the private key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof SessionKey))
return false;
return DataHelper.eq(_data, ((SessionKey)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[SessionKey: ");
if (_data == null) {
buf.append("null key");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,34 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.RandomSource;
public class SessionTag extends ByteArray {
public final static int BYTE_LENGTH = 32;
public SessionTag() { super(); }
public SessionTag(boolean create) {
super();
if (create) {
byte buf[] = new byte[BYTE_LENGTH];
RandomSource.getInstance().nextBytes(buf);
setData(buf);
}
}
public SessionTag(byte val[]) {
super();
setData(val);
}
public void setData(byte val[]) throws IllegalArgumentException {
if (val == null) super.setData(null);
if (val.length != BYTE_LENGTH) throw new IllegalArgumentException("SessionTags must be " + BYTE_LENGTH + " bytes");
super.setData(val);
}
}

View File

@ -0,0 +1,81 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the signature as defined by the I2P data structure spec.
* A signature is a 40byte Integer verifying the authenticity of some data
* using the algorithm defined in the crypto spec.
*
* @author jrandom
*/
public class Signature extends DataStructureImpl {
private final static Log _log = new Log(Signature.class);
private byte[] _data;
public final static int SIGNATURE_BYTES = 40;
public final static byte[] FAKE_SIGNATURE = new byte[SIGNATURE_BYTES];
static {
for (int i = 0; i < SIGNATURE_BYTES; i++)
FAKE_SIGNATURE[i] = 0x00;
}
public Signature() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[SIGNATURE_BYTES];
int read = read(in, _data);
if (read != SIGNATURE_BYTES)
throw new DataFormatException("Not enough bytes to read the signature");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the signature to write out");
if (_data.length != SIGNATURE_BYTES)
throw new DataFormatException("Invalid size of data in the private key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof Signature))
return false;
return DataHelper.eq(_data, ((Signature)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[Signature: ");
if (_data == null) {
buf.append("null signature");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,77 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the SigningPrivateKey as defined by the I2P data structure spec.
* A private key is 256byte Integer. The private key represents only the
* exponent, not the primes, which are constant and defined in the crypto spec.
* This key varies from the PrivateKey in its usage (signing, not decrypting)
*
* @author jrandom
*/
public class SigningPrivateKey extends DataStructureImpl {
private final static Log _log = new Log(SigningPrivateKey.class);
private byte[] _data;
public final static int KEYSIZE_BYTES = 20;
public SigningPrivateKey() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[KEYSIZE_BYTES];
int read = read(in, _data);
if (read != KEYSIZE_BYTES)
throw new DataFormatException("Not enough bytes to read the private key");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the private key to write out");
if (_data.length != KEYSIZE_BYTES)
throw new DataFormatException("Invalid size of data in the private key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof SigningPrivateKey))
return false;
return DataHelper.eq(_data, ((SigningPrivateKey)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[SigningPrivateKey: ");
if (_data == null) {
buf.append("null key");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the SigningPublicKey as defined by the I2P data structure spec.
* A public key is 256byte Integer. The public key represents only the
* exponent, not the primes, which are constant and defined in the crypto spec.
* This key varies from the PrivateKey in its usage (verifying signatures, not encrypting)
*
* @author jrandom
*/
public class SigningPublicKey extends DataStructureImpl {
private final static Log _log = new Log(SigningPublicKey.class);
private byte[] _data;
public final static int KEYSIZE_BYTES = 128;
public SigningPublicKey() { setData(null); }
public byte[] getData() { return _data; }
public void setData(byte[] data) { _data = data; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[KEYSIZE_BYTES];
int read = read(in, _data);
if (read != KEYSIZE_BYTES)
throw new DataFormatException("Not enough bytes to read the public key");
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_data == null)
throw new DataFormatException("No data in the public key to write out");
if (_data.length != KEYSIZE_BYTES)
throw new DataFormatException("Invalid size of data in the public key");
out.write(_data);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof SigningPublicKey))
return false;
return DataHelper.eq(_data, ((SigningPublicKey)obj)._data);
}
public int hashCode() {
return DataHelper.hashCode(_data);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("[SigningPublicKey: ");
if (_data == null) {
buf.append("null key");
} else {
buf.append("size: ").append(_data.length);
int len = 32;
if (len > _data.length)
len = _data.length;
buf.append(" first ").append(len).append(" bytes: ");
buf.append(DataHelper.toString(_data, len));
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,74 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Defines the tunnel ID that messages are passed through on a set of routers.
* This is not globally unique, but must be unique on each router making up
* the tunnel (otherwise they would get confused and send messages down the
* wrong one).
*
* @author jrandom
*/
public class TunnelId extends DataStructureImpl {
private final static Log _log = new Log(TunnelId.class);
private long _tunnelId;
private int _type;
public final static int TYPE_UNSPECIFIED = 0;
public final static int TYPE_INBOUND = 1;
public final static int TYPE_OUTBOUND = 2;
public final static int TYPE_PARTICIPANT = 3;
public TunnelId() {
setTunnelId(-1);
setType(TYPE_UNSPECIFIED);
}
public long getTunnelId() { return _tunnelId; }
public void setTunnelId(long id) { _tunnelId = id; }
/**
* contains the metadata for the tunnel - is it inbound, outbound, or a participant?
* This data point is kept in memory only and is useful for the router.
*
* @return type of tunnel (per constants TYPE_UNSPECIFIED, TYPE_INBOUND, TYPE_OUTBOUND, TYPE_PARTICIPANT)
*/
public int getType() { return _type; }
public void setType(int type) { _type = type; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_tunnelId = DataHelper.readLong(in, 4);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_tunnelId < 0) throw new DataFormatException("Invalid tunnel ID: " + _tunnelId);
DataHelper.writeLong(out, 4, _tunnelId);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof TunnelId))
return false;
return getTunnelId() == ((TunnelId)obj).getTunnelId();
}
public int hashCode() {
return (int)getTunnelId();
}
public String toString() {
return "[TunnelID: " + getTunnelId() + "]";
}
}

View File

@ -0,0 +1,174 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
import java.math.BigInteger;
/**
* Manage an arbitrarily large unsigned integer, using the first bit and first byte
* as the most significant one. Also allows the exporting to byte arrays with whatever
* padding is requested.
*
* WARNING: Range is currently limited to 0 through 2^63-1, due to Java's two's complement
* format. Fix when we need it.
*
* @author jrandom
*/
public class UnsignedInteger {
private final static Log _log = new Log(UnsignedInteger.class);
private byte[] _data;
private long _value;
/**
* Construct the integer from the bytes given, making the value accessible
* immediately.
*
* @param data unsigned number in network byte order (first bit, first byte
* is the most significant)
*/
public UnsignedInteger(byte[] data) {
// strip off excess bytes
int start = 0;
for (int i = 0; i < data.length; i++) {
if (data[i] == 0) {
start++;
} else {
break;
}
}
_data = new byte[data.length - start];
for (int i = 0; i < _data.length; i++)
_data[i] = data[i+start];
// done stripping excess bytes, now calc
_value = calculateValue(_data);
}
/**
* Construct the integer with the java number given, making the bytes
* available immediately.
*
* @param value number to represent
*/
public UnsignedInteger(long value) {
_value = value;
_data = calculateBytes(value);
}
/**
* Calculate the value of the array of bytes, treating it as an unsigned integer
* with the most significant bit and byte first
*
*/
private static long calculateValue(byte[] data) {
if (data == null) {
_log.error("Null data to be calculating for", new Exception("Argh"));
return 0;
} else if (data.length == 0) {
return 0;
}
BigInteger bi = new BigInteger(1, data);
return bi.longValue();
}
/**
* hexify the byte array
*
*/
private final static String toString(byte[] val) {
return "0x" + DataHelper.toString(val, val.length);
}
/**
* Calculate the bytes as an unsigned integer with the most significant bit and byte in the first position
*/
private static byte[] calculateBytes(long value) {
BigInteger bi = new BigInteger(""+value);
byte buf[] = bi.toByteArray();
if ( (buf == null) || (buf.length <= 0) )
throw new IllegalArgumentException("Value [" + value + "] cannot be transformed");
int trim = 0;
while ( (trim < buf.length) && (buf[trim] == 0x00) )
trim++;
byte rv[] = new byte[buf.length - trim];
System.arraycopy(buf, trim, rv, 0, rv.length);
return rv;
}
/**
* Get the unsigned bytes, most significant bit and bytes first, without any padding
*
*/
public byte[] getBytes() { return _data; }
/**
* Get the unsigned bytes, most significant bit and bytes first, zero padded to the
* specified number of bytes
*
* @throws IllegalArgumentException if numBytes < necessary number of bytes
*/
public byte[] getBytes(int numBytes) throws IllegalArgumentException {
if ( (_data == null) || (numBytes < _data.length) )
throw new IllegalArgumentException("Value (" +_value+") is greater than the requested number of bytes ("+numBytes+")");
byte[] data = new byte[numBytes];
System.arraycopy(_data, 0, data, numBytes - _data.length, _data.length);
return data;
}
public BigInteger getBigInteger() { return new BigInteger(1, _data); }
public long getLong() { return _value; }
public int getInt() { return (int)_value; }
public short getShort() { return (short)_value; }
public boolean equals(Object obj) {
if ( (obj != null) && (obj instanceof UnsignedInteger) ) {
return DataHelper.eq(_data, ((UnsignedInteger)obj)._data) &&
DataHelper.eq(_value, ((UnsignedInteger)obj)._value);
} else {
return false;
}
}
public int hashCode() {
return DataHelper.hashCode(_data) + (int)_value;
}
public String toString() {
return "UnsignedInteger: " + getLong() + "/" + toString(getBytes());
}
public static void main(String args[]) {
_log.debug("Testing 1024");
testNum(1024L);
_log.debug("Testing 1025");
testNum(1025L);
_log.debug("Testing 2Gb-1");
testNum(1024*1024*1024*2L-1L);
_log.debug("Testing 4Gb-1");
testNum(1024*1024*1024*4L-1L);
_log.debug("Testing 4Gb");
testNum(1024*1024*1024*4L);
_log.debug("Testing 4Gb+1");
testNum(1024*1024*1024*4L+1L);
_log.debug("Testing MaxLong");
testNum(Long.MAX_VALUE);
try { Thread.sleep(1000); } catch (Throwable t) {}
}
private static void testNum(long num) {
UnsignedInteger i = new UnsignedInteger(num);
_log.debug(num + " turned into an unsigned integer: " + i + " (" + i.getLong() + "/" + toString(i.getBytes()) + ")");
_log.debug(num + " turned into an BigInteger: " + i.getBigInteger());
byte[] val = i.getBytes();
UnsignedInteger val2 = new UnsignedInteger(val);
_log.debug(num + " turned into a byte array and back again: " + val2 + " (" + val2.getLong() + "/" + toString(val2.getBytes()) + ")");
_log.debug(num + " As an 8 byte array: " + toString(val2.getBytes(8)));
}
}

View File

@ -0,0 +1,56 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.DataFormatException;
/**
* Defines the structure for why abuse was reported either by the client to
* the router or by the router to the client
*
* @author jrandom
*/
public class AbuseReason extends DataStructureImpl {
private final static Log _log = new Log(AbuseReason.class);
private String _reason;
public AbuseReason() { setReason(null); }
public String getReason() { return _reason; }
public void setReason(String reason) { _reason = reason; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_reason = DataHelper.readString(in);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_reason == null) throw new DataFormatException("Invalid abuse reason");
DataHelper.writeString(out, _reason);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof AbuseReason) )
return false;
return DataHelper.eq(getReason(), ((AbuseReason)object).getReason());
}
public int hashCode() { return DataHelper.hashCode(getReason()); }
public String toString() {
return "[AbuseReason: " + getReason() + "]";
}
}

View File

@ -0,0 +1,57 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.DataFormatException;
/**
* Provides a severity level (larger numbers are more severe) in association with
* a client reporting abusive behavior to the router or the router reporting it
* to the client
*
* @author jrandom
*/
public class AbuseSeverity extends DataStructureImpl {
private final static Log _log = new Log(AbuseSeverity.class);
private int _severityId;
public AbuseSeverity() { setSeverity(-1); }
public int getSeverity() { return _severityId; }
public void setSeverity(int id) { _severityId = id; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_severityId = (int)DataHelper.readLong(in, 1);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_severityId < 0) throw new DataFormatException("Invalid abuse severity: " + _severityId);
DataHelper.writeLong(out, 1, _severityId);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof AbuseSeverity) )
return false;
return DataHelper.eq(getSeverity(), ((AbuseSeverity)object).getSeverity());
}
public int hashCode() { return getSeverity(); }
public String toString() {
return "[AbuseSeverity: " + getSeverity() + "]";
}
}

View File

@ -0,0 +1,106 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when authorizing
* the LeaseSet
*
* @author jrandom
*/
public class CreateLeaseSetMessage extends I2CPMessageImpl {
private final static Log _log = new Log(CreateLeaseSetMessage.class);
public final static int MESSAGE_TYPE = 4;
private SessionId _sessionId;
private LeaseSet _leaseSet;
private SigningPrivateKey _signingPrivateKey;
private PrivateKey _privateKey;
public CreateLeaseSetMessage() {
setSessionId(null);
setLeaseSet(null);
setSigningPrivateKey(null);
setPrivateKey(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public SigningPrivateKey getSigningPrivateKey() { return _signingPrivateKey; }
public void setSigningPrivateKey(SigningPrivateKey key) { _signingPrivateKey = key; }
public PrivateKey getPrivateKey() { return _privateKey; }
public void setPrivateKey(PrivateKey privateKey) { _privateKey = privateKey; }
public LeaseSet getLeaseSet() { return _leaseSet; }
public void setLeaseSet(LeaseSet leaseSet) { _leaseSet = leaseSet; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_signingPrivateKey = new SigningPrivateKey();
_signingPrivateKey.readBytes(in);
_privateKey = new PrivateKey();
_privateKey.readBytes(in);
_leaseSet = new LeaseSet();
_leaseSet.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error reading the CreateLeaseSetMessage", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_signingPrivateKey == null) || (_privateKey == null) || (_leaseSet == null) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
try {
_sessionId.writeBytes(os);
_signingPrivateKey.writeBytes(os);
_privateKey.writeBytes(os);
_leaseSet.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof CreateLeaseSetMessage) ) {
CreateLeaseSetMessage msg = (CreateLeaseSetMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getSigningPrivateKey(),msg.getSigningPrivateKey()) &&
DataHelper.eq(getPrivateKey(), msg.getPrivateKey()) &&
DataHelper.eq(getLeaseSet(),msg.getLeaseSet());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[CreateLeaseSetMessage: ");
buf.append("\n\tLeaseSet: ").append(getLeaseSet());
buf.append("\n\tSigningPrivateKey: ").append(getSigningPrivateKey());
buf.append("\n\tPrivateKey: ").append(getPrivateKey());
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when establishing a new
* session.
*
* @author jrandom
*/
public class CreateSessionMessage extends I2CPMessageImpl {
private final static Log _log = new Log(CreateSessionMessage.class);
public final static int MESSAGE_TYPE = 1;
private SessionConfig _sessionConfig;
public CreateSessionMessage(SessionConfig config) { setSessionConfig(config); }
public CreateSessionMessage() { setSessionConfig(new SessionConfig()); }
public SessionConfig getSessionConfig() { return _sessionConfig; }
public void setSessionConfig(SessionConfig config) { _sessionConfig = config; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
SessionConfig config = new SessionConfig();
try {
config.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the session configuration", dfe);
}
setSessionConfig(config);
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionConfig == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionConfig.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the session config", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof CreateSessionMessage) ) {
CreateSessionMessage msg = (CreateSessionMessage)object;
return DataHelper.eq(getSessionConfig(), msg.getSessionConfig());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[CreateSessionMessage: ");
buf.append("\n\tConfig: ").append(getSessionConfig());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,77 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when destroying
* existing session.
*
* @author jrandom
*/
public class DestroySessionMessage extends I2CPMessageImpl {
private final static Log _log = new Log(DestroySessionMessage.class);
public final static int MESSAGE_TYPE = 3;
private SessionId _sessionId;
public DestroySessionMessage() {
setSessionId(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
SessionId id = new SessionId();
try {
id.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
setSessionId(id);
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionId == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof DestroySessionMessage) ) {
DestroySessionMessage msg = (DestroySessionMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[DestroySessionMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,73 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when destroying
* existing session.
*
* @author jrandom
*/
public class DisconnectMessage extends I2CPMessageImpl {
private final static Log _log = new Log(DisconnectMessage.class);
public final static int MESSAGE_TYPE = 30;
private String _reason;
public DisconnectMessage() {
setReason(null);
}
public String getReason() { return _reason; }
public void setReason(String reason) { _reason = reason; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_reason = DataHelper.readString(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
DataHelper.writeString(os, _reason);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof DisconnectMessage) ) {
DisconnectMessage msg = (DisconnectMessage)object;
return DataHelper.eq(getReason(),msg.getReason());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[DisconnectMessage: ");
buf.append("\n\tReason: ").append(getReason());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,52 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import net.i2p.util.Log;
/**
* Request the other side to send us what they think the current time is
*
*/
public class GetDateMessage extends I2CPMessageImpl {
private final static Log _log = new Log(GetDateMessage.class);
public final static int MESSAGE_TYPE = 32;
public GetDateMessage() {
super();
}
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
// noop
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
byte rv[] = new byte[0];
return rv;
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof GetDateMessage) ) {
return true;
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[GetDateMessage]");
return buf.toString();
}
}

View File

@ -0,0 +1,64 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.DataStructure;
/**
* Defines the base functionality of API messages
*
* @author jrandom
*/
public interface I2CPMessage extends DataStructure {
/**
* Read the contents from the input stream into the current class's format.
* The stream should be the message body as defined by the client access layer
* specification after the message header (4 bytes specifying the size of the
* message, 1 byte specifying the type of the message).
*
* @param in stream to read from
* @param size number of bytes in the message payload
* @param type type of message (should equal getType())
* @throws I2CPMessageException if the stream doesn't contain a valid message
* that this class can read.
* @throws IOException if there is a problem reading from the stream
*/
public void readMessage(InputStream in, int size, int type) throws I2CPMessageException, IOException;
/**
* Read the contents from the input stream into the current class's format.
* The stream should be the message header and body as defined by the I2CP
* specification
*
* @param in stream to read from
* @throws I2CPMessageException if the stream doesn't contain a valid message
* that this class can read.
* @throws IOException if there is a problem reading from the stream
*/
public void readMessage(InputStream in) throws I2CPMessageException, IOException;
/**
* Write the current message to the output stream as a full message following
* the specification from the I2CP definition.
*
* @throws I2CPMessageException if the current object doesn't have sufficient data
* to write a properly formatted message.
* @throws IOException if there is a problem writing to the stream
*/
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException;
/**
* Return the unique identifier for this type of APIMessage, as specified in the
* network specification document under #ClientAccessLayerMessages
*/
public int getType();
}

View File

@ -0,0 +1,28 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.I2PException;
import net.i2p.util.Log;
/**
* Represent an error serializing or deserializing an APIMessage
*
* @author jrandom
*/
public class I2CPMessageException extends I2PException {
private final static Log _log = new Log(I2CPMessageException.class);
public I2CPMessageException(String message, Throwable parent) {
super(message, parent);
}
public I2CPMessageException(String message) {
super(message);
}
}

View File

@ -0,0 +1,94 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.IOException;
import java.io.FileInputStream;
import net.i2p.data.DataHelper;
import net.i2p.data.DataFormatException;
import net.i2p.util.Log;
/**
* Handle messages from the server for the client
*
*/
public class I2CPMessageHandler {
private final static Log _log = new Log(I2CPMessageHandler.class);
/**
* Read an I2CPMessage from the stream and return the fully populated object.
*
* @throws IOException if there is an IO problem reading from the stream
* @throws I2CPMessageException if there is a problem handling the particular
* message - if it is an unknown type or has improper formatting, etc.
*/
public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMessageException {
try {
int length = (int)DataHelper.readLong(in, 4);
if (length < 0) throw new I2CPMessageException("Invalid message length specified");
int type = (int)DataHelper.readLong(in, 1);
I2CPMessage msg = createMessage(in, length, type);
msg.readMessage(in, length, type);
return msg;
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error reading the message", dfe);
}
}
/**
* Yes, this is fairly ugly, but its the only place it ever happens.
*
*/
private static I2CPMessage createMessage(InputStream in, int length, int type) throws IOException, I2CPMessageException {
switch (type) {
case CreateLeaseSetMessage.MESSAGE_TYPE:
return new CreateLeaseSetMessage();
case CreateSessionMessage.MESSAGE_TYPE:
return new CreateSessionMessage();
case DestroySessionMessage.MESSAGE_TYPE:
return new DestroySessionMessage();
case DisconnectMessage.MESSAGE_TYPE:
return new DisconnectMessage();
case MessageStatusMessage.MESSAGE_TYPE:
return new MessageStatusMessage();
case MessagePayloadMessage.MESSAGE_TYPE:
return new MessagePayloadMessage();
case ReceiveMessageBeginMessage.MESSAGE_TYPE:
return new ReceiveMessageBeginMessage();
case ReceiveMessageEndMessage.MESSAGE_TYPE:
return new ReceiveMessageEndMessage();
case ReportAbuseMessage.MESSAGE_TYPE:
return new ReportAbuseMessage();
case RequestLeaseSetMessage.MESSAGE_TYPE:
return new RequestLeaseSetMessage();
case SendMessageMessage.MESSAGE_TYPE:
return new SendMessageMessage();
case SessionStatusMessage.MESSAGE_TYPE:
return new SessionStatusMessage();
case GetDateMessage.MESSAGE_TYPE:
return new GetDateMessage();
case SetDateMessage.MESSAGE_TYPE:
return new SetDateMessage();
default:
throw new I2CPMessageException("The type "+ type + " is an unknown I2CP message");
}
}
public static void main(String args[]) {
try {
I2CPMessage msg = readMessage(new FileInputStream(args[0]));
System.out.println(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,113 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.util.Log;
/**
* Defines the base message implementation.
*
* @author jrandom
*/
public abstract class I2CPMessageImpl extends DataStructureImpl implements I2CPMessage {
private final static Log _log = new Log(I2CPMessageImpl.class);
public I2CPMessageImpl() {}
/**
* Validate the type and size of the message. and then read the message
* into the data structures.
*
*/
public void readMessage(InputStream in) throws I2CPMessageException, IOException {
int length = 0;
try {
length = (int)DataHelper.readLong(in, 4);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error reading the length bytes", dfe);
}
if (length < 0) throw new I2CPMessageException("Invalid message length specified");
int type = -1;
try {
type = (int)DataHelper.readLong(in, 1);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error reading the type byte", dfe);
}
readMessage(in, length, type);
}
/**
* Read the body into the data structures
*
*/
public void readMessage(InputStream in, int length, int type) throws I2CPMessageException, IOException {
if (type != getType()) throw new I2CPMessageException("Invalid message type (found: " + type + " supported: " + getType() + " class: " + getClass().getName()+ ")");
if (length < 0) throw new IOException("Negative payload size");
byte buf[] = new byte[length];
int read = DataHelper.read(in, buf);
if (read != length)
throw new IOException("Not able to read enough bytes [" + read + "] read, expected [ " + length + "]");
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
doReadMessage(bis, length);
}
/**
* Read in the payload part of the message (after the initial 4 byte size and 1
* byte type)
*
*/
protected abstract void doReadMessage(InputStream buf, int size) throws I2CPMessageException, IOException;
/**
* Write out the payload part of the message (not including the 4 byte size and
* 1 byte type)
*
*/
protected abstract byte[] doWriteMessage() throws I2CPMessageException, IOException;
/**
* Write out the full message to the stream, including the 4 byte size and 1
* byte type header.
*
*/
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException {
byte[] data = doWriteMessage();
try {
DataHelper.writeLong(out, 4, data.length);
DataHelper.writeLong(out, 1, getType());
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to write the message length or type", dfe);
}
out.write(data);
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
try {
readMessage(in);
} catch (I2CPMessageException ime) {
throw new DataFormatException("Error reading the message", ime);
}
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
try {
writeMessage(out);
} catch (I2CPMessageException ime) {
throw new DataFormatException("Error writing the message", ime);
}
}
}

View File

@ -0,0 +1,145 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* The I2CPMessageReader reads an InputStream (using
* {@link I2CPMessageHandler I2CPMessageHandler}) and passes out events to a registered
* listener, where events are either messages being received, exceptions being
* thrown, or the connection being closed. Applications should use this rather
* than read from the stream themselves.
*
* @author jrandom
*/
public class I2CPMessageReader {
private final static Log _log = new Log(I2CPMessageReader.class);
private InputStream _stream;
private I2CPMessageEventListener _listener;
private I2CPMessageReaderRunner _reader;
private Thread _readerThread;
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
_stream = stream;
setListener(lsnr);
_reader = new I2CPMessageReaderRunner();
_readerThread = new I2PThread(_reader);
_readerThread.setDaemon(true);
_readerThread.setName("I2CP Reader");
}
public void setListener(I2CPMessageEventListener lsnr) { _listener = lsnr; }
public I2CPMessageEventListener getListener() { return _listener; }
/**
* Instruct the reader to begin reading messages off the stream
*
*/
public void startReading() { _readerThread.start(); }
/**
* Have the already started reader pause its reading indefinitely
*
*/
public void pauseReading() { _reader.pauseRunner(); }
/**
* Resume reading after a pause
*
*/
public void resumeReading() { _reader.resumeRunner(); }
/**
* Cancel reading.
*
*/
public void stopReading() { _reader.cancelRunner(); }
/**
* Defines the different events the reader produces while reading the stream
*
*/
public static interface I2CPMessageEventListener {
/**
* Notify the listener that a message has been received from the given
* reader
*
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message);
/**
* Notify the listener that an exception was thrown while reading from the given
* reader
*
*/
public void readError(I2CPMessageReader reader, Exception error);
/**
* Notify the listener that the stream the given reader was running off
* closed
*
*/
public void disconnected(I2CPMessageReader reader);
}
private class I2CPMessageReaderRunner implements Runnable {
private boolean _doRun;
private boolean _stayAlive;
public I2CPMessageReaderRunner() {
_doRun = true;
_stayAlive = true;
}
public void pauseRunner() { _doRun = false; }
public void resumeRunner() { _doRun = true; }
public void cancelRunner() {
_doRun = false;
_stayAlive = false;
if (_stream != null) {
try {
_stream.close();
} catch (IOException ioe) {
_log.error("Error closing the stream", ioe);
}
}
_stream = null;
}
public void run() {
while (_stayAlive) {
while (_doRun) {
// do read
try {
I2CPMessage msg = I2CPMessageHandler.readMessage(_stream);
if (msg != null) {
_log.debug("Before handling the newly received message");
_listener.messageReceived(I2CPMessageReader.this, msg);
_log.debug("After handling the newly received message");
}
} catch (I2CPMessageException ime) {
_log.error("Error handling message", ime);
_listener.readError(I2CPMessageReader.this, ime);
cancelRunner();
} catch (IOException ioe) {
_log.error("IO Error handling message", ioe);
_listener.disconnected(I2CPMessageReader.this);
cancelRunner();
} catch (Throwable t) {
_log.log(Log.CRIT, "Unhandled error reading I2CP stream", t);
_listener.disconnected(I2CPMessageReader.this);
cancelRunner();
}
}
if (!_doRun) {
// pause .5 secs when we're paused
try { Thread.sleep(500); } catch (InterruptedException ie) {}
}
}
// boom bye bye bad bwoy
}
}
}

View File

@ -0,0 +1,56 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.DataFormatException;
/**
* Defines the message ID of a message delivered between a router and a client
* in a particular session. These IDs are not globally unique.
*
* @author jrandom
*/
public class MessageId extends DataStructureImpl {
private final static Log _log = new Log(MessageId.class);
private int _messageId;
public MessageId() { setMessageId(-1); }
public int getMessageId() { return _messageId; }
public void setMessageId(int id) { _messageId = id; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_messageId = (int)DataHelper.readLong(in, 4);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_messageId < 0) throw new DataFormatException("Invalid message ID: " + _messageId);
DataHelper.writeLong(out, 4, _messageId);
}
public boolean equals(Object object) {
if ( (object == null) || !(object instanceof MessageId) )
return false;
return DataHelper.eq(getMessageId(), ((MessageId)object).getMessageId());
}
public int hashCode() { return getMessageId(); }
public String toString() {
return "[MessageId: " + getMessageId() + "]";
}
}

View File

@ -0,0 +1,100 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Payload;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router to ask it to deliver
* a new message
*
* @author jrandom
*/
public class MessagePayloadMessage extends I2CPMessageImpl {
private final static Log _log = new Log(MessagePayloadMessage.class);
public final static int MESSAGE_TYPE = 31;
private SessionId _sessionId;
private MessageId _messageId;
private Payload _payload;
public MessagePayloadMessage() {
setSessionId(null);
setMessageId(null);
setPayload(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public MessageId getMessageId() { return _messageId; }
public void setMessageId(MessageId id) { _messageId = id; }
public Payload getPayload() { return _payload; }
public void setPayload(Payload payload) { _payload = payload; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_messageId = new MessageId();
_messageId.readBytes(in);
_payload = new Payload();
_payload.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionId == null)
throw new I2CPMessageException("Unable to write out the message, as the session ID has not been defined");
if (_messageId == null)
throw new I2CPMessageException("Unable to write out the message, as the message ID has not been defined");
if (_payload == null)
throw new I2CPMessageException("Unable to write out the message, as the payload has not been defined");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
try {
_sessionId.writeBytes(os);
_messageId.writeBytes(os);
_payload.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof MessagePayloadMessage) ) {
MessagePayloadMessage msg = (MessagePayloadMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getMessageId(),msg.getMessageId()) &&
DataHelper.eq(getPayload(),msg.getPayload());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[MessagePayloadMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tMessageId: ").append(getMessageId());
buf.append("\n\tPayload: ").append(getPayload());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,128 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when destroying
* existing session.
*
* @author jrandom
*/
public class MessageStatusMessage extends I2CPMessageImpl {
private final static Log _log = new Log(SessionStatusMessage.class);
public final static int MESSAGE_TYPE = 22;
private SessionId _sessionId;
private MessageId _messageId;
private long _nonce;
private long _size;
private int _status;
public final static int STATUS_AVAILABLE = 0;
public final static int STATUS_SEND_ACCEPTED = 1;
public final static int STATUS_SEND_BEST_EFFORT_SUCCESS = 2;
public final static int STATUS_SEND_BEST_EFFORT_FAILURE = 3;
public final static int STATUS_SEND_GUARANTEED_SUCCESS = 4;
public final static int STATUS_SEND_GUARANTEED_FAILURE = 5;
public MessageStatusMessage() {
setSessionId(null);
setStatus(-1);
setMessageId(null);
setSize(-1);
setNonce(-1);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public int getStatus() { return _status; }
public void setStatus(int status) { _status = status; }
public MessageId getMessageId() { return _messageId; }
public void setMessageId(MessageId id) { _messageId = id; }
public long getSize() { return _size; }
public void setSize(long size) { _size = size; }
public long getNonce() { return _nonce; }
public void setNonce(long nonce) { _nonce = nonce; }
public static final String getStatusString(int status) {
switch (status) {
case STATUS_AVAILABLE: return "AVAILABLE ";
case STATUS_SEND_ACCEPTED: return "SEND ACCEPTED ";
case STATUS_SEND_BEST_EFFORT_SUCCESS: return "BEST EFFORT SUCCESS";
case STATUS_SEND_BEST_EFFORT_FAILURE: return "BEST EFFORT FAILURE";
case STATUS_SEND_GUARANTEED_SUCCESS: return "GUARANTEED SUCCESS ";
case STATUS_SEND_GUARANTEED_FAILURE: return "GUARANTEED FAILURE ";
default: return "***INVALID STATUS: " + status;
}
}
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_messageId = new MessageId();
_messageId.readBytes(in);
_status = (int)DataHelper.readLong(in, 1);
_size = DataHelper.readLong(in, 4);
_nonce = DataHelper.readLong(in, 4);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_messageId == null) || (_status < 0) || (_nonce <= 0) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
_messageId.writeBytes(os);
DataHelper.writeLong(os, 1, _status);
DataHelper.writeLong(os, 4, _size);
DataHelper.writeLong(os, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof MessageStatusMessage) ) {
MessageStatusMessage msg = (MessageStatusMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getMessageId(),msg.getMessageId()) &&
(getNonce() == msg.getNonce()) &&
DataHelper.eq(getSize(),msg.getSize()) &&
DataHelper.eq(getStatus(),msg.getStatus());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[MessageStatusMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tNonce: ").append(getNonce());
buf.append("\n\tMessageId: ").append(getMessageId());
buf.append("\n\tStatus: ").append(getStatusString(getStatus()));
buf.append("\n\tSize: ").append(getSize());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,85 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when asking the
* router to start sending a message to it.
*
* @author jrandom
*/
public class ReceiveMessageBeginMessage extends I2CPMessageImpl {
private final static Log _log = new Log(ReceiveMessageBeginMessage.class);
public final static int MESSAGE_TYPE = 6;
private SessionId _sessionId;
private MessageId _messageId;
public ReceiveMessageBeginMessage() {
setSessionId(null);
setMessageId(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public MessageId getMessageId() { return _messageId; }
public void setMessageId(MessageId id) { _messageId = id; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_messageId = new MessageId();
_messageId.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_messageId == null) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
_messageId.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof ReceiveMessageBeginMessage) ) {
ReceiveMessageBeginMessage msg = (ReceiveMessageBeginMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getMessageId(),msg.getMessageId());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[ReceiveMessageBeginMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tMessageId: ").append(getMessageId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,85 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when asking the
* router to start sending a message to it.
*
* @author jrandom
*/
public class ReceiveMessageEndMessage extends I2CPMessageImpl {
private final static Log _log = new Log(ReceiveMessageEndMessage.class);
public final static int MESSAGE_TYPE = 7;
private SessionId _sessionId;
private MessageId _messageId;
public ReceiveMessageEndMessage() {
setSessionId(null);
setMessageId(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public MessageId getMessageId() { return _messageId; }
public void setMessageId(MessageId id) { _messageId = id; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_messageId = new MessageId();
_messageId.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_messageId == null) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
_messageId.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof ReceiveMessageEndMessage) ) {
ReceiveMessageEndMessage msg = (ReceiveMessageEndMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getMessageId(),msg.getMessageId());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[ReceiveMessageEndMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tMessageId: ").append(getMessageId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,107 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when asking the
* router what its address visibility is
*
* @author jrandom
*/
public class ReportAbuseMessage extends I2CPMessageImpl {
private final static Log _log = new Log(ReportAbuseMessage.class);
public final static int MESSAGE_TYPE = 29;
private SessionId _sessionId;
private AbuseSeverity _severity;
private AbuseReason _reason;
private MessageId _messageId;
public ReportAbuseMessage() {
setSessionId(null);
setSeverity(null);
setReason(null);
setMessageId(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public AbuseSeverity getSeverity() { return _severity; }
public void setSeverity(AbuseSeverity severity) { _severity = severity; }
public AbuseReason getReason() { return _reason; }
public void setReason(AbuseReason reason) { _reason = reason; }
public MessageId getMessageId() { return _messageId; }
public void setMessageId(MessageId id) { _messageId = id; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_severity = new AbuseSeverity();
_severity.readBytes(in);
_reason = new AbuseReason();
_reason.readBytes(in);
_messageId = new MessageId();
_messageId.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_severity == null) || (_reason == null) )
throw new I2CPMessageException("Not enough information to construct the message");
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
try {
_sessionId.writeBytes(os);
_severity.writeBytes(os);
_reason.writeBytes(os);
if (_messageId == null) {
_messageId = new MessageId();
_messageId.setMessageId(0);
}
_messageId.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof ReportAbuseMessage) ) {
ReportAbuseMessage msg = (ReportAbuseMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getSeverity(),msg.getSeverity()) &&
DataHelper.eq(getReason(),msg.getReason()) &&
DataHelper.eq(getMessageId(),msg.getMessageId());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[ReportAbuseMessage: ");
buf.append("\n\tSessionID: ").append(getSessionId());
buf.append("\n\tSeverity: ").append(getSeverity());
buf.append("\n\tReason: ").append(getReason());
buf.append("\n\tMessageId: ").append(getMessageId());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,153 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterIdentity;
import net.i2p.data.TunnelId;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when destroying
* existing session.
*
* @author jrandom
*/
public class RequestLeaseSetMessage extends I2CPMessageImpl {
private final static Log _log = new Log(RequestLeaseSetMessage.class);
public final static int MESSAGE_TYPE = 21;
private SessionId _sessionId;
private List _endpoints;
private Date _end;
public RequestLeaseSetMessage() {
setSessionId(null);
_endpoints = new ArrayList();
setEndDate(null);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public int getEndpoints() { return _endpoints.size(); }
public RouterIdentity getRouter(int endpoint) {
if ( (endpoint < 0) || (_endpoints.size() < endpoint) ) return null;
return ((TunnelEndpoint)_endpoints.get(endpoint)).getRouter();
}
public TunnelId getTunnelId(int endpoint) {
if ( (endpoint < 0) || (_endpoints.size() < endpoint) ) return null;
return ((TunnelEndpoint)_endpoints.get(endpoint)).getTunnelId();
}
public void remoteEndpoint(int endpoint) {
if ( (endpoint >= 0) && (endpoint < _endpoints.size()) )
_endpoints.remove(endpoint);
}
public void addEndpoint(RouterIdentity router, TunnelId tunnel) {
_endpoints.add(new TunnelEndpoint(router,tunnel));
}
public Date getEndDate() { return _end; }
public void setEndDate(Date end) { _end = end; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
int numTunnels = (int)DataHelper.readLong(in, 1);
_endpoints.clear();
for (int i = 0; i < numTunnels; i++) {
RouterIdentity router = new RouterIdentity();
router.readBytes(in);
TunnelId tunnel = new TunnelId();
tunnel.readBytes(in);
_endpoints.add(new TunnelEndpoint(router, tunnel));
}
_end = DataHelper.readDate(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_endpoints == null) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
DataHelper.writeLong(os, 1, _endpoints.size());
for (int i = 0; i < _endpoints.size(); i++) {
RouterIdentity router = getRouter(i);
router.writeBytes(os);
TunnelId tunnel = getTunnelId(i);
tunnel.writeBytes(os);
}
DataHelper.writeDate(os, _end);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof RequestLeaseSetMessage) ) {
RequestLeaseSetMessage msg = (RequestLeaseSetMessage)object;
if (getEndpoints() != msg.getEndpoints()) return false;
for (int i = 0; i < getEndpoints(); i++) {
if (!DataHelper.eq(getRouter(i), msg.getRouter(i)) ||
DataHelper.eq(getTunnelId(i), msg.getTunnelId(i)))
return false;
}
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getEndDate(),msg.getEndDate());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[RequestLeaseMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tTunnels:");
for (int i = 0; i < getEndpoints(); i++) {
buf.append("\n\t\tRouterIdentity: ").append(getRouter(i));
buf.append("\n\t\tTunnelId: ").append(getTunnelId(i));
}
buf.append("\n\tEndDate: ").append(getEndDate());
buf.append("]");
return buf.toString();
}
private class TunnelEndpoint {
private RouterIdentity _router;
private TunnelId _tunnelId;
public TunnelEndpoint() {
_router = null;
_tunnelId = null;
}
public TunnelEndpoint(RouterIdentity router, TunnelId id) {
_router = router;
_tunnelId = id;
}
public RouterIdentity getRouter() { return _router; }
public void setRouter(RouterIdentity router) { _router = router; }
public TunnelId getTunnelId() { return _tunnelId; }
public void setTunnelId(TunnelId tunnelId) { _tunnelId = tunnelId; }
}
}

View File

@ -0,0 +1,104 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Payload;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router to ask it to deliver
* a new message
*
* @author jrandom
*/
public class SendMessageMessage extends I2CPMessageImpl {
private final static Log _log = new Log(SendMessageMessage.class);
public final static int MESSAGE_TYPE = 5;
private SessionId _sessionId;
private Destination _destination;
private Payload _payload;
private long _nonce;
public SendMessageMessage() {
setSessionId(null);
setDestination(null);
setPayload(null);
setNonce(0);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public Destination getDestination() { return _destination; }
public void setDestination(Destination destination) { _destination = destination; }
public Payload getPayload() { return _payload; }
public void setPayload(Payload payload) { _payload = payload; }
public long getNonce() { return _nonce; }
public void setNonce(long nonce) { _nonce = nonce; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_destination = new Destination();
_destination.readBytes(in);
_payload = new Payload();
_payload.readBytes(in);
_nonce = DataHelper.readLong(in, 4);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ( (_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0) )
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
try {
_sessionId.writeBytes(os);
_destination.writeBytes(os);
_payload.writeBytes(os);
DataHelper.writeLong(os, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof SendMessageMessage) ) {
SendMessageMessage msg = (SendMessageMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getDestination(),msg.getDestination()) &&
(getNonce() == msg.getNonce()) &&
DataHelper.eq(getPayload(),msg.getPayload());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[SendMessageMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tNonce: ").append(getNonce());
buf.append("\n\tDestination: ").append(getDestination());
buf.append("\n\tPayload: ").append(getPayload());
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,217 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Properties;
import net.i2p.crypto.DSAEngine;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.Destination;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import java.util.Date;
/**
* Defines the information a client must provide to create a session
*
* @author jrandom
*/
public class SessionConfig extends DataStructureImpl {
private final static Log _log = new Log(SessionConfig.class);
private Destination _destination;
private Signature _signature;
private Date _creationDate;
private Properties _options;
/**
* if the client authorized this session more than the specified period ago,
* refuse it, since it may be a replay attack
*
*/
private final static long OFFSET_VALIDITY = 30*1000;
public SessionConfig() {
_destination = null;
_signature = null;
_creationDate = new Date(Clock.getInstance().now());
_options = null;
}
/**
* Retrieve the destination for which this session is supposed to connect
*
*/
public Destination getDestination() { return _destination; }
/**
* Specify the destination for which this session is supposed to connect
*
*/
public void setDestination(Destination dest) { _destination = dest; }
/**
* Determine when this session was authorized by the destination (so we can
* prevent replay attacks)
*
*/
public Date getCreationDate() { return _creationDate; }
public void setCreationDate(Date date) { _creationDate = date; }
/**
* Retrieve any configuration options for the session
*
*/
public Properties getOptions() { return _options; }
/**
* Configure the session with the given options
*
*/
public void setOptions(Properties options) { _options = options; }
public Signature getSignature() { return _signature; }
public void setSignature(Signature sig) { _signature = sig; }
/**
* Sign the structure using the supplied private key
*
*/
public void signSessionConfig(SigningPrivateKey signingKey) throws DataFormatException {
byte data[] = getBytes();
if (data == null) throw new DataFormatException("Unable to retrieve bytes for signing");
_signature = DSAEngine.getInstance().sign(data, signingKey);
}
/**
* Verify that the signature matches the destination's signing public key.
*
* @return true only if the signature matches
*/
public boolean verifySignature() {
if (getSignature() == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Signature is null!");
return false;
}
if (getDestination() == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Destination is null!");
return false;
}
if (getCreationDate() == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Date is null!");
return false;
}
if (tooOld()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too old!");
return false;
}
byte data[] = getBytes();
if (data == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bytes could not be found - wtf?");
return false;
}
boolean ok = DSAEngine.getInstance().verifySignature(getSignature(), data, getDestination().getSigningPublicKey());
if (!ok) {
if (_log.shouldLog(Log.WARN))
_log.warn("DSA signature failed!");
}
return ok;
}
public boolean tooOld() {
long now = Clock.getInstance().now();
long earliestValid = now - OFFSET_VALIDITY;
long latestValid = now + OFFSET_VALIDITY;
if (_creationDate == null) return true;
if (_creationDate.getTime() < earliestValid) return true;
if (_creationDate.getTime() > latestValid) return true;
return false;
}
private byte[] getBytes() {
if (_destination == null) return null;
if (_options == null) return null;
if (_creationDate == null) return null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
_log.debug("PubKey size for destination: " + _destination.getPublicKey().getData().length);
_log.debug("SigningKey size for destination: " + _destination.getSigningPublicKey().getData().length);
_destination.writeBytes(out);
DataHelper.writeProperties(out, _options);
DataHelper.writeDate(out, _creationDate);
} catch (IOException ioe) {
_log.error("IOError signing", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing out the bytes for signing/verification", dfe);
return null;
}
return out.toByteArray();
}
public void readBytes(InputStream rawConfig) throws DataFormatException, IOException {
_destination = new Destination();
_destination.readBytes(rawConfig);
_options = DataHelper.readProperties(rawConfig);
_creationDate = DataHelper.readDate(rawConfig);
_signature = new Signature();
_signature.readBytes(rawConfig);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_destination == null) || (_options == null) || (_signature == null) || (_creationDate == null) )
throw new DataFormatException("Not enough data to create the session config");
_destination.writeBytes(out);
DataHelper.writeProperties(out, _options);
DataHelper.writeDate(out, _creationDate);
_signature.writeBytes(out);
}
public boolean equals(Object object) {
if ( (object != null) && (object instanceof SessionConfig) ) {
SessionConfig cfg = (SessionConfig)object;
return DataHelper.eq(getSignature(), cfg.getSignature()) &&
DataHelper.eq(getDestination(), cfg.getDestination()) &&
DataHelper.eq(getCreationDate(), cfg.getCreationDate()) &&
DataHelper.eq(getOptions(), cfg.getOptions());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer("[SessionConfig: ");
buf.append("\n\tDestination: ").append(getDestination());
buf.append("\n\tSignature: ").append(getSignature());
buf.append("\n\tCreation Date: ").append(getCreationDate());
buf.append("\n\tOptions: #: ").append(getOptions().size());
for (Iterator iter = getOptions().keySet().iterator(); iter.hasNext();) {
String key = (String)iter.next();
String val = getOptions().getProperty(key);
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
buf.append("]");
return buf.toString();
}
}

View File

@ -0,0 +1,58 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import net.i2p.util.Log;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.DataFormatException;
/**
* Defines the token passed between the router and client to associate messages
* with a particular session. These IDs are not globally unique.
*
* @author jrandom
*/
public class SessionId extends DataStructureImpl {
private final static Log _log = new Log(SessionId.class);
private int _sessionId;
public SessionId() { setSessionId(-1); }
public int getSessionId() { return _sessionId; }
public void setSessionId(int id) { _sessionId = id; }
public void readBytes(InputStream in) throws DataFormatException, IOException {
_sessionId = (int)DataHelper.readLong(in, 2);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_sessionId < 0) throw new DataFormatException("Invalid session ID: " + _sessionId);
DataHelper.writeLong(out, 2, _sessionId);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof SessionId))
return false;
return getSessionId() == ((SessionId)obj).getSessionId();
}
public int hashCode() {
return getSessionId();
}
public String toString() {
return "[SessionId: " + getSessionId() + "]";
}
}

View File

@ -0,0 +1,89 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when destroying
* existing session.
*
* @author jrandom
*/
public class SessionStatusMessage extends I2CPMessageImpl {
private final static Log _log = new Log(SessionStatusMessage.class);
public final static int MESSAGE_TYPE = 20;
private SessionId _sessionId;
private int _status;
public final static int STATUS_DESTROYED = 0;
public final static int STATUS_CREATED = 1;
public final static int STATUS_UPDATED = 2;
public final static int STATUS_INVALID = 3;
public SessionStatusMessage() {
setSessionId(null);
setStatus(STATUS_INVALID);
}
public SessionId getSessionId() { return _sessionId; }
public void setSessionId(SessionId id) { _sessionId = id; }
public int getStatus() { return _status; }
public void setStatus(int status) { _status = status; }
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_status = (int)DataHelper.readLong(in, 1);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionId == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
DataHelper.writeLong(os, 1, _status);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() { return MESSAGE_TYPE; }
public boolean equals(Object object) {
if ( (object != null) && (object instanceof SessionStatusMessage) ) {
SessionStatusMessage msg = (SessionStatusMessage)object;
return DataHelper.eq(getSessionId(),msg.getSessionId()) &&
DataHelper.eq(getStatus(),msg.getStatus());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[SessionStatusMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tStatus: ").append(getStatus());
buf.append("]");
return buf.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More