forked from I2P_Developers/i2p.i2p
* LeaseSet: Add encrypt/decrypt methods
This commit is contained in:
@@ -9,6 +9,7 @@ package net.i2p.data;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -17,13 +18,34 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Defines the set of leases a destination currently has.
|
||||
*
|
||||
* Support encryption and decryption with a supplied key.
|
||||
* Only the gateways and tunnel IDs in the individual
|
||||
* leases are encrypted.
|
||||
*
|
||||
* Encrypted leases are not indicated as such.
|
||||
* The only way to tell a lease is encrypted is to
|
||||
* determine that the listed gateways do not exist.
|
||||
* Routers wishing to decrypt a leaseset must have the
|
||||
* desthash and key in their keyring.
|
||||
* This is required for the local router as well, since
|
||||
* the encryption is done on the client side of I2CP, the
|
||||
* router must decrypt it back again for local usage
|
||||
* (but not for transmission to the floodfills)
|
||||
*
|
||||
* Decrypted leases are only available through the getLease()
|
||||
* method, so that storage and network transmission via
|
||||
* writeBytes() will output the original encrypted
|
||||
* leases and the original leaseset signature.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class LeaseSet extends DataStructureImpl {
|
||||
@@ -40,6 +62,9 @@ public class LeaseSet extends DataStructureImpl {
|
||||
// Store these since isCurrent() and getEarliestLeaseDate() are called frequently
|
||||
private long _firstExpiration;
|
||||
private long _lastExpiration;
|
||||
private List _decryptedLeases;
|
||||
private boolean _decrypted;
|
||||
private boolean _checked;
|
||||
|
||||
/** This seems like plenty */
|
||||
private final static int MAX_LEASES = 6;
|
||||
@@ -55,6 +80,8 @@ public class LeaseSet extends DataStructureImpl {
|
||||
_receivedAsPublished = false;
|
||||
_firstExpiration = Long.MAX_VALUE;
|
||||
_lastExpiration = 0;
|
||||
_decrypted = false;
|
||||
_checked = false;
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
@@ -104,10 +131,16 @@ public class LeaseSet extends DataStructureImpl {
|
||||
}
|
||||
|
||||
public int getLeaseCount() {
|
||||
if (isEncrypted())
|
||||
return _leases.size() - 1;
|
||||
else
|
||||
return _leases.size();
|
||||
}
|
||||
|
||||
public Lease getLease(int index) {
|
||||
if (isEncrypted())
|
||||
return (Lease) _decryptedLeases.get(index);
|
||||
else
|
||||
return (Lease) _leases.get(index);
|
||||
}
|
||||
|
||||
@@ -335,4 +368,139 @@ public class LeaseSet extends DataStructureImpl {
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final int DATA_LEN = Hash.HASH_LENGTH + 4;
|
||||
private static final int IV_LEN = 16;
|
||||
|
||||
/**
|
||||
* Encrypt the gateway and tunnel ID of each lease, leaving the expire dates unchanged.
|
||||
* This adds an extra dummy lease, because AES data must be padded to 16 bytes.
|
||||
* The fact that it is encrypted is not stored anywhere.
|
||||
* Must be called after all the leases are in place, but before sign().
|
||||
*/
|
||||
public void encrypt(SessionKey key) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("encrypting lease: " + _destination.calculateHash());
|
||||
try {
|
||||
encryp(key);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error encrypting lease: " + _destination.calculateHash());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error encrypting lease: " + _destination.calculateHash());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - Put the {Gateway Hash, TunnelID} pairs for all the leases in a buffer
|
||||
* - Pad with random data to a multiple of 16 bytes
|
||||
* - Use the first part of the dest's public key as an IV
|
||||
* - Encrypt
|
||||
* - Pad with random data to a multiple of 36 bytes
|
||||
* - Add an extra lease
|
||||
* - Replace the Hash and TunnelID in each Lease
|
||||
*/
|
||||
private void encryp(SessionKey key) throws DataFormatException, IOException {
|
||||
int size = _leases.size();
|
||||
if (size < 1 || size > MAX_LEASES-1)
|
||||
throw new IllegalArgumentException("Bad number of leases for encryption");
|
||||
int datalen = ((DATA_LEN * size / 16) + 1) * 16;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(datalen);
|
||||
for (int i = 0; i < size; i++) {
|
||||
((Lease)_leases.get(i)).getGateway().writeBytes(baos);
|
||||
((Lease)_leases.get(i)).getTunnelId().writeBytes(baos);
|
||||
}
|
||||
// pad out to multiple of 16 with random data before encryption
|
||||
int padlen = datalen - (DATA_LEN * size);
|
||||
byte[] pad = new byte[padlen];
|
||||
RandomSource.getInstance().nextBytes(pad);
|
||||
baos.write(pad);
|
||||
byte[] iv = new byte[IV_LEN];
|
||||
System.arraycopy(_destination.getPublicKey().getData(), 0, iv, 0, IV_LEN);
|
||||
byte[] enc = new byte[DATA_LEN * (size + 1)];
|
||||
I2PAppContext.getGlobalContext().aes().encrypt(baos.toByteArray(), 0, enc, 0, key, iv, datalen);
|
||||
// pad out to multiple of 36 with random data after encryption
|
||||
// (even for 4 leases, where 36*4 is a multiple of 16, we add another, just to be consistent)
|
||||
padlen = enc.length - datalen;
|
||||
pad = new byte[padlen];
|
||||
RandomSource.getInstance().nextBytes(pad);
|
||||
System.arraycopy(pad, 0, enc, datalen, padlen);
|
||||
// add the padded lease...
|
||||
Lease padLease = new Lease();
|
||||
padLease.setEndDate(((Lease)_leases.get(0)).getEndDate());
|
||||
_leases.add(padLease);
|
||||
// ...and replace all the gateways and tunnel ids
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(enc);
|
||||
for (int i = 0; i < size+1; i++) {
|
||||
Hash h = new Hash();
|
||||
h.readBytes(bais);
|
||||
((Lease)_leases.get(i)).setGateway(h);
|
||||
TunnelId t = new TunnelId();
|
||||
t.readBytes(bais);
|
||||
((Lease)_leases.get(i)).setTunnelId(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the leases, except for the last one which is partially padding.
|
||||
* Store the new decrypted leases in a backing store,
|
||||
* and keep the original leases so that verify() still works and the
|
||||
* encrypted leaseset can be sent on to others (via writeBytes())
|
||||
*/
|
||||
private void decrypt(SessionKey key) throws DataFormatException, IOException {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("decrypting lease: " + _destination.calculateHash());
|
||||
int size = _leases.size();
|
||||
if (size < 2)
|
||||
throw new DataFormatException("Bad number of leases for decryption");
|
||||
int datalen = DATA_LEN * size;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(datalen);
|
||||
for (int i = 0; i < size; i++) {
|
||||
((Lease)_leases.get(i)).getGateway().writeBytes(baos);
|
||||
((Lease)_leases.get(i)).getTunnelId().writeBytes(baos);
|
||||
}
|
||||
byte[] iv = new byte[IV_LEN];
|
||||
System.arraycopy(_destination.getPublicKey().getData(), 0, iv, 0, IV_LEN);
|
||||
int enclen = ((DATA_LEN * (size - 1) / 16) + 1) * 16;
|
||||
byte[] enc = new byte[enclen];
|
||||
System.arraycopy(baos.toByteArray(), 0, enc, 0, enclen);
|
||||
byte[] dec = new byte[enclen];
|
||||
I2PAppContext.getGlobalContext().aes().decrypt(enc, 0, dec, 0, key, iv, enclen);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(dec);
|
||||
_decryptedLeases = new ArrayList(size - 1);
|
||||
for (int i = 0; i < size-1; i++) {
|
||||
Lease l = new Lease();
|
||||
Hash h = new Hash();
|
||||
h.readBytes(bais);
|
||||
l.setGateway(h);
|
||||
TunnelId t = new TunnelId();
|
||||
t.readBytes(bais);
|
||||
l.setTunnelId(t);
|
||||
l.setEndDate(((Lease)_leases.get(i)).getEndDate());
|
||||
_decryptedLeases.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if it was encrypted, and we decrypted it successfully.
|
||||
* Decrypts on first call.
|
||||
*/
|
||||
private synchronized boolean isEncrypted() {
|
||||
if (_decrypted)
|
||||
return true;
|
||||
if (_checked || _destination == null)
|
||||
return false;
|
||||
SessionKey key = I2PAppContext.getGlobalContext().keyRing().get(_destination.calculateHash());
|
||||
if (key != null) {
|
||||
try {
|
||||
decrypt(key);
|
||||
_decrypted = true;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error decrypting lease: " + _destination.calculateHash() + dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error decrypting lease: " + _destination.calculateHash() + ioe);
|
||||
}
|
||||
}
|
||||
_checked = true;
|
||||
return _decrypted;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user