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.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SigAlgo;
import net.i2p.crypto.SigType;
/**
*<p>
* Base implementation of common methods for the two data structures
* that are stored in the netDb, i.e. LeaseSet and RouterInfo.
* Implemented in 0.8.2 and retrofitted over LeaseSet and RouterInfo.
*
* This consolidates some common code and makes it easier to
* implement the NetDB and I2NP without doing instanceof all over the place.
*</p><p>
* DatabaseEntries have a SHA256 hash, a routing key, a timestamp, and
* signatures.
*</p><p>
* Do not reuse objects.
* Many of the setters and other methods contain checks to prevent
* altering a DatabaseEntry after it is signed. This protects the netdb,
* messages that contain DatabaseEntries,
* and the object itself from simple causes of corruption, by
* throwing IllegalStateExceptions.
* These checks are not necessarily thread-safe, and are not guaranteed
* to catch all possible means of corruption.
* Beware of other avenues of corruption, such as directly modifying data
* stored in byte[] objects.
*</p>
*
* @author zzz
* @since 0.8.2
*/
public abstract class DatabaseEntry extends DataStructureImpl {
/** these are the same as in i2np's DatabaseStoreMessage */
public final static int KEY_TYPE_ROUTERINFO = 0;
public final static int KEY_TYPE_LEASESET = 1;
protected volatile Signature _signature;
protected volatile Hash _currentRoutingKey;
protected volatile long _routingKeyGenMod;
/**
* A common interface to the timestamp of the two subclasses.
* Identical to getEarliestLeaseData() in LeaseSet,
* and getPublished() in RouterInfo.
* Note that for a LeaseSet this will be in the future,
* and for a RouterInfo it will be in the past.
* Either way, it's a timestamp.
*
* @since 0.8.2
*/
public abstract long getDate();
/**
* Get the keys and the cert
* Identical to getDestination() in LeaseSet,
* and getIdentity() in RouterInfo.
*
* @return KAC or null
* @since 0.8.2, public since 0.9.17
*/
public abstract KeysAndCert getKeysAndCert();
/**
* A common interface to the Hash of the two subclasses.
* Identical to getDestination().calculateHash() in LeaseSet,
* and getIdentity().getHash() in RouterInfo.
*
* @return Hash or null
* @since 0.8.2
*/
public Hash getHash() {
KeysAndCert kac = getKeysAndCert();
if (kac == null)
return null;
return kac.getHash();
}
/**
* Get the type of the data structure.
* This should be faster than instanceof.
*
* @return KEY_TYPE_ROUTERINFO or KEY_TYPE_LEASESET
* @since 0.8.2
*/
public abstract int getType();
/**
* Returns the raw payload data, excluding the signature, to be signed by sign().
*
* Most callers should use writeBytes() or toByteArray() instead.
*
* FIXME RouterInfo throws DFE and LeaseSet returns null
* @return null on error ???????????????????????
*/
protected abstract byte[] getBytes() throws DataFormatException;
/**
* 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)
*
* @throws IllegalStateException if not in RouterContext
*/
public Hash getRoutingKey() {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext())
throw new IllegalStateException("Not in router context");
RoutingKeyGenerator gen = ctx.routingKeyGenerator();
long mod = gen.getLastChanged();
if (mod != _routingKeyGenMod) {
_currentRoutingKey = gen.getRoutingKey(getHash());
_routingKeyGenMod = mod;
}
return _currentRoutingKey;
}
/**
* @throws IllegalStateException if not in RouterContext
*/
public boolean validateRoutingKey() {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext())
throw new IllegalStateException("Not in router context");
RoutingKeyGenerator gen = ctx.routingKeyGenerator();
Hash destKey = getHash();
Hash rk = gen.getRoutingKey(destKey);
return rk.equals(getRoutingKey());
}
/**
* 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
*
* @throws IllegalStateException if already signed
*/
public void setSignature(Signature signature) {
if (_signature != null)
throw new IllegalStateException();
_signature = signature;
}
/**
* Sign the structure using the supplied signing key
*
* @throws IllegalStateException if already signed
*/
public void sign(SigningPrivateKey key) throws DataFormatException {
if (_signature != null)
throw new IllegalStateException();
byte[] bytes = getBytes();
if (bytes == null) throw new DataFormatException("Not enough data to sign");
if (key == null)
throw new DataFormatException("No signing key");
// now sign with the key
_signature = DSAEngine.getInstance().sign(bytes, key);
if (_signature == null)
throw new DataFormatException("Signature failed with " + key.getType() + " key");
}
/**
* Identical to getDestination().getSigningPublicKey() in LeaseSet,
* and getIdentity().getSigningPublicKey() in RouterInfo.
*
* @return SPK or null
* @since 0.8.2
*/
protected SigningPublicKey getSigningPublicKey() {
KeysAndCert kac = getKeysAndCert();
if (kac == null)
return null;
return kac.getSigningPublicKey();
}
/**
* This is the same as isValid() in RouterInfo
* or verifySignature() in LeaseSet.
* @return valid
*/
protected boolean verifySignature() {
if (_signature == null)
return false;
byte data[];
try {
data = getBytes();
} catch (DataFormatException dfe) {
return false;
}
if (data == null)
return false;
// if the data is non-null the SPK will be non-null
SigningPublicKey spk = getSigningPublicKey();
SigType type = spk.getType();
// As of 0.9.28, disallow RSA as it's so slow it could be
// used as a DoS
if (type == null || type.getBaseAlgorithm() == SigAlgo.RSA)
return false;
return DSAEngine.getInstance().verifySignature(_signature, data, spk);
}
}