* LeaseSet: Add encrypt/decrypt methods

This commit is contained in:
zzz
2009-01-20 17:16:24 +00:00
parent 8d891b99d1
commit 0e2a4227ef

View File

@@ -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;
}
}