forked from I2P_Developers/i2p.i2p
229 lines
9.0 KiB
Java
229 lines
9.0 KiB
Java
package net.i2p.data.i2np;
|
|
/*
|
|
* 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.Date;
|
|
|
|
import net.i2p.I2PAppContext;
|
|
import net.i2p.data.Certificate;
|
|
import net.i2p.data.DataFormatException;
|
|
import net.i2p.data.DataHelper;
|
|
import net.i2p.data.DataStructureImpl;
|
|
import net.i2p.data.Hash;
|
|
import net.i2p.data.PublicKey;
|
|
import net.i2p.data.SessionKey;
|
|
import net.i2p.data.SessionTag;
|
|
import net.i2p.util.Log;
|
|
|
|
|
|
/**
|
|
* Defines a single hop of a source routed message, as usable for building a
|
|
* SourceRouteReplyMessage
|
|
*
|
|
* @author jrandom
|
|
*/
|
|
public class SourceRouteBlock extends DataStructureImpl {
|
|
private final static Log _log = new Log(SourceRouteBlock.class);
|
|
private Hash _router;
|
|
private byte[] _data;
|
|
private SessionKey _key;
|
|
private byte[] _tag;
|
|
private DeliveryInstructions _decryptedInstructions;
|
|
private long _decryptedMessageId;
|
|
private Certificate _decryptedCertificate;
|
|
private long _decryptedExpiration;
|
|
|
|
public SourceRouteBlock() {
|
|
setRouter(null);
|
|
setData(null);
|
|
setKey(null);
|
|
setTag((byte[])null);
|
|
_decryptedInstructions = null;
|
|
_decryptedMessageId = -1;
|
|
_decryptedCertificate = null;
|
|
_decryptedExpiration = -1;
|
|
}
|
|
|
|
/**
|
|
* Get the router through which replies using this source route block must
|
|
* be sent (as the getData() is encrypted for their eyes only)
|
|
*
|
|
*/
|
|
public Hash getRouter() { return _router; }
|
|
public void setRouter(Hash router) { _router= router; }
|
|
/**
|
|
* Get the encrypted header. After decryption (via ElGamal+AES as defined
|
|
* in the data structures spec), this array contains:
|
|
* DeliveryInstructions
|
|
* 4 byte Integer for a message ID
|
|
* Certificate
|
|
* Date of expiration for replies
|
|
*
|
|
*/
|
|
public byte[] getData() { return _data; }
|
|
private void setData(byte data[]) { _data = data; }
|
|
/**
|
|
* Retrieve the session key which may be used in conjunction with the tag
|
|
* to encrypt a garlic message and send it as a reply to this message.
|
|
* The encryption would follow scenario 2 of the ElGamal+AES encryption method
|
|
* defined in the data structures spec.
|
|
*
|
|
*/
|
|
public SessionKey getKey() { return _key; }
|
|
public void setKey(SessionKey key) { _key = key; }
|
|
/**
|
|
* Get the tag made available for use in conjunction with the getKey() to
|
|
* ElGamal+AES encrypt a garlic message without knowing the public key to
|
|
* which the message is destined
|
|
*
|
|
*/
|
|
public byte[] getTag() { return _tag; }
|
|
public void setTag(SessionTag tag) { setTag(tag.getData()); }
|
|
public void setTag(byte tag[]) {
|
|
if ( (tag != null) && (tag.length != SessionTag.BYTE_LENGTH) )
|
|
throw new IllegalArgumentException("Tag must be either null or 32 bytes");
|
|
_tag = tag;
|
|
}
|
|
|
|
/**
|
|
* After decryptData, this contains the delivery instructions for this block
|
|
*/
|
|
public DeliveryInstructions getDecryptedInstructions() { return _decryptedInstructions; }
|
|
/**
|
|
* After decryptData, this contains the message ID to be used with this block
|
|
*/
|
|
public long getDecryptedMessageId() { return _decryptedMessageId; }
|
|
/**
|
|
* After decryptData, this contains the Certificate 'paying' for the forwarding according to
|
|
* this block
|
|
*/
|
|
public Certificate getDecryptedCertificate() { return _decryptedCertificate; }
|
|
/**
|
|
* After decryptData, this contains the date after which this block should not be forwarded
|
|
*/
|
|
public long getDecryptedExpiration() { return _decryptedExpiration; }
|
|
|
|
/**
|
|
* Set the raw data with the formatted and encrypted options specified
|
|
*
|
|
* @param instructions Where a message bearing this block should be sent
|
|
* @param messageId ID of the message for this block (not repeatable)
|
|
* @param expiration date after which this block expires
|
|
* @param replyThrough Encryption key of the router to whom this block is specified (not
|
|
* the router specified in the delivery instructions!)
|
|
*
|
|
* @throws DataFormatException if the data is invalid or could not be encrypted
|
|
*/
|
|
public void setData(I2PAppContext ctx, DeliveryInstructions instructions,
|
|
long messageId, Certificate cert, long expiration,
|
|
PublicKey replyThrough) throws DataFormatException {
|
|
try {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
|
|
|
|
_decryptedInstructions = instructions;
|
|
_decryptedMessageId = messageId;
|
|
_decryptedCertificate = cert;
|
|
_decryptedExpiration = expiration;
|
|
|
|
instructions.writeBytes(baos);
|
|
DataHelper.writeLong(baos, 4, messageId);
|
|
cert.writeBytes(baos);
|
|
DataHelper.writeDate(baos, new Date(expiration));
|
|
|
|
int paddedSize = 256;
|
|
SessionKey sessKey = null;
|
|
SessionTag tag = null;
|
|
if (instructions.getDelayRequested()) {
|
|
// always use a new key if we're delaying, since the reply block may not be used within the
|
|
// window of a session
|
|
sessKey = ctx.keyGenerator().generateSessionKey();
|
|
tag = null;
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Delay requested - creating a new session key");
|
|
} else {
|
|
sessKey = ctx.sessionKeyManager().getCurrentKey(replyThrough);
|
|
if (sessKey == null) {
|
|
sessKey = ctx.keyGenerator().generateSessionKey();
|
|
tag = null;
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("No delay requested, but no session key is known");
|
|
} else {
|
|
tag = ctx.sessionKeyManager().consumeNextAvailableTag(replyThrough, sessKey);
|
|
}
|
|
}
|
|
byte encData[] = ctx.elGamalAESEngine().encrypt(baos.toByteArray(), replyThrough,
|
|
sessKey, null, tag, paddedSize);
|
|
setData(encData);
|
|
} catch (IOException ioe) {
|
|
throw new DataFormatException("Error writing out the source route block data", ioe);
|
|
} catch (DataFormatException dfe) {
|
|
throw new DataFormatException("Error writing out the source route block data", dfe);
|
|
}
|
|
}
|
|
|
|
|
|
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
|
_router = new Hash();
|
|
_router.readBytes(in);
|
|
int size = (int)DataHelper.readLong(in, 2);
|
|
_data = new byte[size];
|
|
int read = read(in, _data);
|
|
if (read != _data.length)
|
|
throw new DataFormatException("Incorrect # of bytes read for source route block: " + read);
|
|
_key = new SessionKey();
|
|
_key.readBytes(in);
|
|
_tag = new byte[32];
|
|
read = read(in, _tag);
|
|
if (read != _tag.length)
|
|
throw new DataFormatException("Incorrect # of bytes read for session tag: " + read);
|
|
}
|
|
|
|
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
|
if ( (_router == null) || (_data == null) || (_key == null) || (_tag == null) || (_tag.length != 32) )
|
|
throw new DataFormatException("Insufficient data to write");
|
|
_router.writeBytes(out);
|
|
DataHelper.writeLong(out, 2, _data.length);
|
|
out.write(_data);
|
|
_key.writeBytes(out);
|
|
out.write(_tag);
|
|
}
|
|
|
|
public boolean equals(Object obj) {
|
|
if ( (obj == null) || !(obj instanceof SourceRouteBlock))
|
|
return false;
|
|
SourceRouteBlock block = (SourceRouteBlock)obj;
|
|
return DataHelper.eq(getRouter(), block.getRouter()) &&
|
|
DataHelper.eq(getData(), block.getData()) &&
|
|
DataHelper.eq(getKey(), block.getKey()) &&
|
|
DataHelper.eq(getTag(), block.getTag());
|
|
}
|
|
|
|
public int hashCode() {
|
|
return DataHelper.hashCode(getRouter()) +
|
|
DataHelper.hashCode(getData()) +
|
|
DataHelper.hashCode(getKey()) +
|
|
DataHelper.hashCode(getTag());
|
|
}
|
|
|
|
public String toString() {
|
|
StringBuffer buf = new StringBuffer(128);
|
|
buf.append("[SourceRouteBlock: ");
|
|
buf.append("\n\tRouter: ").append(getRouter());
|
|
buf.append("\n\tData: ").append(DataHelper.toString(getData(), getData().length));
|
|
buf.append("\n\tTag: ").append(DataHelper.toString(getTag(), (getTag() != null ? getTag().length : 0)));
|
|
buf.append("\n\tKey: ").append(getKey());
|
|
buf.append("]");
|
|
return buf.toString();
|
|
}
|
|
}
|