great renaming (cont.)
This commit is contained in:
BIN
apps/phttprelay/java/lib/javax.servlet.jar
Normal file
BIN
apps/phttprelay/java/lib/javax.servlet.jar
Normal file
Binary file not shown.
34
core/c/build.sh
Normal file
34
core/c/build.sh
Normal 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
44
core/c/include/jbigi.h
Normal 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
123
core/c/src/jbigi.c
Normal 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, ©);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/********/
|
||||
|
11
core/doc/readme.license.txt
Normal file
11
core/doc/readme.license.txt
Normal 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
30
core/java/build.xml
Normal 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>
|
22
core/java/src/net/i2p/CoreVersion.java
Normal file
22
core/java/src/net/i2p/CoreVersion.java
Normal 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);
|
||||
}
|
||||
}
|
48
core/java/src/net/i2p/I2PException.java
Normal file
48
core/java/src/net/i2p/I2PException.java
Normal 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);
|
||||
}
|
||||
}
|
338
core/java/src/net/i2p/client/ATalk.java
Normal file
338
core/java/src/net/i2p/client/ATalk.java
Normal 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); }
|
||||
}
|
375
core/java/src/net/i2p/client/ConnectionRunner.java
Normal file
375
core/java/src/net/i2p/client/ConnectionRunner.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
26
core/java/src/net/i2p/client/DisconnectMessageHandler.java
Normal file
26
core/java/src/net/i2p/client/DisconnectMessageHandler.java
Normal 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);
|
||||
}
|
||||
}
|
26
core/java/src/net/i2p/client/HandlerImpl.java
Normal file
26
core/java/src/net/i2p/client/HandlerImpl.java
Normal 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; }
|
||||
}
|
21
core/java/src/net/i2p/client/I2CPMessageHandler.java
Normal file
21
core/java/src/net/i2p/client/I2CPMessageHandler.java
Normal 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);
|
||||
}
|
153
core/java/src/net/i2p/client/I2CPMessageProducer.java
Normal file
153
core/java/src/net/i2p/client/I2CPMessageProducer.java
Normal 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);
|
||||
}
|
||||
}
|
67
core/java/src/net/i2p/client/I2PClient.java
Normal file
67
core/java/src/net/i2p/client/I2PClient.java
Normal 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;
|
||||
}
|
23
core/java/src/net/i2p/client/I2PClientFactory.java
Normal file
23
core/java/src/net/i2p/client/I2PClientFactory.java
Normal 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();
|
||||
}
|
||||
}
|
75
core/java/src/net/i2p/client/I2PClientImpl.java
Normal file
75
core/java/src/net/i2p/client/I2PClientImpl.java
Normal 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
|
||||
}
|
||||
}
|
41
core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java
Normal file
41
core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java
Normal 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;
|
||||
}
|
||||
}
|
109
core/java/src/net/i2p/client/I2PSession.java
Normal file
109
core/java/src/net/i2p/client/I2PSession.java
Normal 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();
|
||||
}
|
28
core/java/src/net/i2p/client/I2PSessionException.java
Normal file
28
core/java/src/net/i2p/client/I2PSessionException.java
Normal 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);
|
||||
}
|
||||
}
|
492
core/java/src/net/i2p/client/I2PSessionImpl.java
Normal file
492
core/java/src/net/i2p/client/I2PSessionImpl.java
Normal 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;
|
||||
}
|
||||
}
|
287
core/java/src/net/i2p/client/I2PSessionImpl2.java
Normal file
287
core/java/src/net/i2p/client/I2PSessionImpl2.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
44
core/java/src/net/i2p/client/I2PSessionListener.java
Normal file
44
core/java/src/net/i2p/client/I2PSessionListener.java
Normal 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
208
core/java/src/net/i2p/client/MessageState.java
Normal file
208
core/java/src/net/i2p/client/MessageState.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
123
core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java
Normal file
123
core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
29
core/java/src/net/i2p/client/SetDateMessageHandler.java
Normal file
29
core/java/src/net/i2p/client/SetDateMessageHandler.java
Normal 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();
|
||||
}
|
||||
}
|
121
core/java/src/net/i2p/client/TestClient.java
Normal file
121
core/java/src/net/i2p/client/TestClient.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
84
core/java/src/net/i2p/client/TestServer.java
Normal file
84
core/java/src/net/i2p/client/TestServer.java
Normal 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();
|
||||
}
|
||||
}
|
23
core/java/src/net/i2p/client/naming/DummyNamingService.java
Normal file
23
core/java/src/net/i2p/client/naming/DummyNamingService.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
78
core/java/src/net/i2p/client/naming/NamingService.java
Normal file
78
core/java/src/net/i2p/client/naming/NamingService.java
Normal 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;
|
||||
}
|
||||
}
|
145
core/java/src/net/i2p/crypto/AESEngine.java
Normal file
145
core/java/src/net/i2p/crypto/AESEngine.java
Normal 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));
|
||||
}
|
||||
}
|
411
core/java/src/net/i2p/crypto/AESInputStream.java
Normal file
411
core/java/src/net/i2p/crypto/AESInputStream.java
Normal 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) {}
|
||||
}
|
||||
}
|
125
core/java/src/net/i2p/crypto/AESOutputStream.java
Normal file
125
core/java/src/net/i2p/crypto/AESOutputStream.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
150
core/java/src/net/i2p/crypto/CryptixAESEngine.java
Normal file
150
core/java/src/net/i2p/crypto/CryptixAESEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
874
core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java
Normal file
874
core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java
Normal 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> © 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);
|
||||
}
|
||||
}
|
64
core/java/src/net/i2p/crypto/CryptoConstants.java
Normal file
64
core/java/src/net/i2p/crypto/CryptoConstants.java
Normal 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");
|
||||
}
|
312
core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java
Normal file
312
core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
281
core/java/src/net/i2p/crypto/DSAEngine.java
Normal file
281
core/java/src/net/i2p/crypto/DSAEngine.java
Normal 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;
|
||||
}
|
||||
}
|
92
core/java/src/net/i2p/crypto/DummyElGamalEngine.java
Normal file
92
core/java/src/net/i2p/crypto/DummyElGamalEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
520
core/java/src/net/i2p/crypto/ElGamalAESEngine.java
Normal file
520
core/java/src/net/i2p/crypto/ElGamalAESEngine.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
258
core/java/src/net/i2p/crypto/ElGamalEngine.java
Normal file
258
core/java/src/net/i2p/crypto/ElGamalEngine.java
Normal 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));
|
||||
}
|
||||
}
|
33
core/java/src/net/i2p/crypto/HMACSHA256Generator.java
Normal file
33
core/java/src/net/i2p/crypto/HMACSHA256Generator.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
|
158
core/java/src/net/i2p/crypto/KeyGenerator.java
Normal file
158
core/java/src/net/i2p/crypto/KeyGenerator.java
Normal 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) {}
|
||||
}
|
||||
}
|
164
core/java/src/net/i2p/crypto/PersistentSessionKeyManager.java
Normal file
164
core/java/src/net/i2p/crypto/PersistentSessionKeyManager.java
Normal 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) {}
|
||||
}
|
||||
}
|
173
core/java/src/net/i2p/crypto/SHA256Generator.java
Normal file
173
core/java/src/net/i2p/crypto/SHA256Generator.java
Normal 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;
|
||||
}
|
||||
}
|
111
core/java/src/net/i2p/crypto/SessionKeyManager.java
Normal file
111
core/java/src/net/i2p/crypto/SessionKeyManager.java
Normal 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() {}
|
||||
}
|
527
core/java/src/net/i2p/crypto/TransientSessionKeyManager.java
Normal file
527
core/java/src/net/i2p/crypto/TransientSessionKeyManager.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
193
core/java/src/net/i2p/crypto/YKGenerator.java
Normal file
193
core/java/src/net/i2p/crypto/YKGenerator.java
Normal 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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
642
core/java/src/net/i2p/data/Base64.java
Normal file
642
core/java/src/net/i2p/data/Base64.java
Normal 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
|
46
core/java/src/net/i2p/data/ByteArray.java
Normal file
46
core/java/src/net/i2p/data/ByteArray.java
Normal 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); }
|
||||
}
|
115
core/java/src/net/i2p/data/Certificate.java
Normal file
115
core/java/src/net/i2p/data/Certificate.java
Normal 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();
|
||||
}
|
||||
}
|
28
core/java/src/net/i2p/data/DataFormatException.java
Normal file
28
core/java/src/net/i2p/data/DataFormatException.java
Normal 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);
|
||||
}
|
||||
}
|
552
core/java/src/net/i2p/data/DataHelper.java
Normal file
552
core/java/src/net/i2p/data/DataHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
64
core/java/src/net/i2p/data/DataStructure.java
Normal file
64
core/java/src/net/i2p/data/DataStructure.java
Normal 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();
|
||||
}
|
80
core/java/src/net/i2p/data/DataStructureImpl.java
Normal file
80
core/java/src/net/i2p/data/DataStructureImpl.java
Normal 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);
|
||||
}
|
||||
}
|
102
core/java/src/net/i2p/data/Destination.java
Normal file
102
core/java/src/net/i2p/data/Destination.java
Normal 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;
|
||||
}
|
||||
}
|
79
core/java/src/net/i2p/data/Hash.java
Normal file
79
core/java/src/net/i2p/data/Hash.java
Normal 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;
|
||||
}
|
||||
}
|
125
core/java/src/net/i2p/data/Lease.java
Normal file
125
core/java/src/net/i2p/data/Lease.java
Normal 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();
|
||||
}
|
||||
}
|
280
core/java/src/net/i2p/data/LeaseSet.java
Normal file
280
core/java/src/net/i2p/data/LeaseSet.java
Normal 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();
|
||||
}
|
||||
}
|
100
core/java/src/net/i2p/data/Payload.java
Normal file
100
core/java/src/net/i2p/data/Payload.java
Normal 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();
|
||||
}
|
||||
}
|
76
core/java/src/net/i2p/data/PrivateKey.java
Normal file
76
core/java/src/net/i2p/data/PrivateKey.java
Normal 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();
|
||||
}
|
||||
}
|
75
core/java/src/net/i2p/data/PublicKey.java
Normal file
75
core/java/src/net/i2p/data/PublicKey.java
Normal 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();
|
||||
}
|
||||
}
|
133
core/java/src/net/i2p/data/RouterAddress.java
Normal file
133
core/java/src/net/i2p/data/RouterAddress.java
Normal 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();
|
||||
}
|
||||
}
|
117
core/java/src/net/i2p/data/RouterIdentity.java
Normal file
117
core/java/src/net/i2p/data/RouterIdentity.java
Normal 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;
|
||||
}
|
||||
}
|
432
core/java/src/net/i2p/data/RouterInfo.java
Normal file
432
core/java/src/net/i2p/data/RouterInfo.java
Normal 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;
|
||||
}
|
||||
}
|
112
core/java/src/net/i2p/data/RoutingKeyGenerator.java
Normal file
112
core/java/src/net/i2p/data/RoutingKeyGenerator.java
Normal 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) {}
|
||||
}
|
||||
}
|
75
core/java/src/net/i2p/data/SessionKey.java
Normal file
75
core/java/src/net/i2p/data/SessionKey.java
Normal 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();
|
||||
}
|
||||
}
|
34
core/java/src/net/i2p/data/SessionTag.java
Normal file
34
core/java/src/net/i2p/data/SessionTag.java
Normal 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);
|
||||
}
|
||||
}
|
81
core/java/src/net/i2p/data/Signature.java
Normal file
81
core/java/src/net/i2p/data/Signature.java
Normal 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();
|
||||
}
|
||||
}
|
77
core/java/src/net/i2p/data/SigningPrivateKey.java
Normal file
77
core/java/src/net/i2p/data/SigningPrivateKey.java
Normal 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();
|
||||
}
|
||||
}
|
76
core/java/src/net/i2p/data/SigningPublicKey.java
Normal file
76
core/java/src/net/i2p/data/SigningPublicKey.java
Normal 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();
|
||||
}
|
||||
}
|
74
core/java/src/net/i2p/data/TunnelId.java
Normal file
74
core/java/src/net/i2p/data/TunnelId.java
Normal 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() + "]";
|
||||
}
|
||||
}
|
174
core/java/src/net/i2p/data/UnsignedInteger.java
Normal file
174
core/java/src/net/i2p/data/UnsignedInteger.java
Normal 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)));
|
||||
}
|
||||
}
|
56
core/java/src/net/i2p/data/i2cp/AbuseReason.java
Normal file
56
core/java/src/net/i2p/data/i2cp/AbuseReason.java
Normal 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() + "]";
|
||||
}
|
||||
}
|
57
core/java/src/net/i2p/data/i2cp/AbuseSeverity.java
Normal file
57
core/java/src/net/i2p/data/i2cp/AbuseSeverity.java
Normal 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() + "]";
|
||||
}
|
||||
}
|
106
core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
Normal file
106
core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
Normal 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();
|
||||
}
|
||||
}
|
76
core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java
Normal file
76
core/java/src/net/i2p/data/i2cp/CreateSessionMessage.java
Normal 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();
|
||||
}
|
||||
}
|
77
core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java
Normal file
77
core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java
Normal 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();
|
||||
}
|
||||
}
|
73
core/java/src/net/i2p/data/i2cp/DisconnectMessage.java
Normal file
73
core/java/src/net/i2p/data/i2cp/DisconnectMessage.java
Normal 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();
|
||||
}
|
||||
}
|
52
core/java/src/net/i2p/data/i2cp/GetDateMessage.java
Normal file
52
core/java/src/net/i2p/data/i2cp/GetDateMessage.java
Normal 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();
|
||||
}
|
||||
}
|
64
core/java/src/net/i2p/data/i2cp/I2CPMessage.java
Normal file
64
core/java/src/net/i2p/data/i2cp/I2CPMessage.java
Normal 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();
|
||||
}
|
28
core/java/src/net/i2p/data/i2cp/I2CPMessageException.java
Normal file
28
core/java/src/net/i2p/data/i2cp/I2CPMessageException.java
Normal 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);
|
||||
}
|
||||
}
|
94
core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
Normal file
94
core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
113
core/java/src/net/i2p/data/i2cp/I2CPMessageImpl.java
Normal file
113
core/java/src/net/i2p/data/i2cp/I2CPMessageImpl.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
145
core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
Normal file
145
core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
56
core/java/src/net/i2p/data/i2cp/MessageId.java
Normal file
56
core/java/src/net/i2p/data/i2cp/MessageId.java
Normal 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() + "]";
|
||||
}
|
||||
}
|
100
core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java
Normal file
100
core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java
Normal 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();
|
||||
}
|
||||
}
|
128
core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java
Normal file
128
core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
107
core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java
Normal file
107
core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java
Normal 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();
|
||||
}
|
||||
}
|
153
core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
Normal file
153
core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
Normal 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; }
|
||||
}
|
||||
}
|
104
core/java/src/net/i2p/data/i2cp/SendMessageMessage.java
Normal file
104
core/java/src/net/i2p/data/i2cp/SendMessageMessage.java
Normal 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();
|
||||
}
|
||||
}
|
217
core/java/src/net/i2p/data/i2cp/SessionConfig.java
Normal file
217
core/java/src/net/i2p/data/i2cp/SessionConfig.java
Normal 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();
|
||||
}
|
||||
}
|
58
core/java/src/net/i2p/data/i2cp/SessionId.java
Normal file
58
core/java/src/net/i2p/data/i2cp/SessionId.java
Normal 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() + "]";
|
||||
}
|
||||
}
|
89
core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java
Normal file
89
core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java
Normal 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
Reference in New Issue
Block a user