forked from I2P_Developers/i2p.i2p
I2CP: Set and validate offline sig in SessionConfig
This commit is contained in:
@ -95,17 +95,18 @@ class I2CPMessageProducer {
|
|||||||
CreateSessionMessage msg = new CreateSessionMessage();
|
CreateSessionMessage msg = new CreateSessionMessage();
|
||||||
SessionConfig cfg = new SessionConfig(session.getMyDestination());
|
SessionConfig cfg = new SessionConfig(session.getMyDestination());
|
||||||
cfg.setOptions(session.getOptions());
|
cfg.setOptions(session.getOptions());
|
||||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config created");
|
if (session.isOffline()) {
|
||||||
|
cfg.setOfflineSignature(session.getOfflineExpiration(),
|
||||||
|
session.getTransientSigningPublicKey(),
|
||||||
|
session.getOfflineSignature());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
cfg.signSessionConfig(session.getPrivateKey());
|
cfg.signSessionConfig(session.getPrivateKey());
|
||||||
} catch (DataFormatException dfe) {
|
} catch (DataFormatException dfe) {
|
||||||
throw new I2PSessionException("Unable to sign the session config", dfe);
|
throw new I2PSessionException("Unable to sign the session config", dfe);
|
||||||
}
|
}
|
||||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config signed");
|
|
||||||
msg.setSessionConfig(cfg);
|
msg.setSessionConfig(cfg);
|
||||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config loaded into message");
|
|
||||||
session.sendMessage_unchecked(msg);
|
session.sendMessage_unchecked(msg);
|
||||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config message sent");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,12 +19,14 @@ import java.util.Properties;
|
|||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.crypto.DSAEngine;
|
import net.i2p.crypto.DSAEngine;
|
||||||
|
import net.i2p.crypto.SigType;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.DataStructureImpl;
|
import net.i2p.data.DataStructureImpl;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.Signature;
|
import net.i2p.data.Signature;
|
||||||
import net.i2p.data.SigningPrivateKey;
|
import net.i2p.data.SigningPrivateKey;
|
||||||
|
import net.i2p.data.SigningPublicKey;
|
||||||
import net.i2p.util.Clock;
|
import net.i2p.util.Clock;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.OrderedProperties;
|
import net.i2p.util.OrderedProperties;
|
||||||
@ -40,6 +42,24 @@ public class SessionConfig extends DataStructureImpl {
|
|||||||
private Date _creationDate;
|
private Date _creationDate;
|
||||||
private Properties _options;
|
private Properties _options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds since epoch, NOT ms
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public static final String PROP_OFFLINE_EXPIRATION = "i2cp.leaseSetOfflineExpiration";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base 64, optionally preceded by sig type and ':', default DSA-SHA1
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public static final String PROP_TRANSIENT_KEY = "i2cp.leaseSetTransientPublicKey";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base 64, optionally preceded by sig type and ':', default DSA-SHA1
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public static final String PROP_OFFLINE_SIGNATURE = "i2cp.leaseSetOfflineSignature";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the client authorized this session more than the specified period ago,
|
* If the client authorized this session more than the specified period ago,
|
||||||
* refuse it, since it may be a replay attack.
|
* refuse it, since it may be a replay attack.
|
||||||
@ -98,6 +118,8 @@ public class SessionConfig extends DataStructureImpl {
|
|||||||
* Defaults are not serialized out-of-JVM, and the router does not recognize defaults in-JVM.
|
* Defaults are not serialized out-of-JVM, and the router does not recognize defaults in-JVM.
|
||||||
* Client side must promote defaults to the primary map.
|
* Client side must promote defaults to the primary map.
|
||||||
*
|
*
|
||||||
|
* Does NOT make a copy.
|
||||||
|
*
|
||||||
* @param options Properties for this session
|
* @param options Properties for this session
|
||||||
*/
|
*/
|
||||||
public void setOptions(Properties options) {
|
public void setOptions(Properties options) {
|
||||||
@ -112,10 +134,96 @@ public class SessionConfig extends DataStructureImpl {
|
|||||||
_signature = sig;
|
_signature = sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offline signing data.
|
||||||
|
* Does NOT validate the signature.
|
||||||
|
* Must be called AFTER setOptions(). Will throw ISE otherwise.
|
||||||
|
* Side effect - modifies options.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public void setOfflineSignature(long expires, SigningPublicKey transientSPK, Signature offlineSig) {
|
||||||
|
if (_options == null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
_options.setProperty(PROP_OFFLINE_EXPIRATION, Long.toString(expires / 1000));
|
||||||
|
_options.setProperty(PROP_TRANSIENT_KEY,
|
||||||
|
transientSPK.getType().getCode() + ":" + transientSPK.toBase64());
|
||||||
|
_options.setProperty(PROP_OFFLINE_SIGNATURE, offlineSig.toBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offline expiration
|
||||||
|
* @return Java time (ms) or 0 if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public long getOfflineExpiration() {
|
||||||
|
if (_options == null)
|
||||||
|
return 0;
|
||||||
|
String s = _options.getProperty(PROP_OFFLINE_EXPIRATION);
|
||||||
|
if (s == null)
|
||||||
|
return 0;
|
||||||
|
try {
|
||||||
|
return Long.parseLong(s) * 1000;
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public SigningPublicKey getTransientSigningPublicKey() {
|
||||||
|
if (_options == null || _destination == null)
|
||||||
|
return null;
|
||||||
|
String s = _options.getProperty(PROP_TRANSIENT_KEY);
|
||||||
|
if (s == null)
|
||||||
|
return null;
|
||||||
|
int colon = s.indexOf(':');
|
||||||
|
SigType type;
|
||||||
|
if (colon > 0) {
|
||||||
|
String stype = s.substring(0, colon);
|
||||||
|
type = SigType.parseSigType(stype);
|
||||||
|
if (type == null)
|
||||||
|
return null;
|
||||||
|
s = s.substring(colon + 1);
|
||||||
|
} else {
|
||||||
|
type = SigType.DSA_SHA1;
|
||||||
|
}
|
||||||
|
SigningPublicKey rv = new SigningPublicKey(type);
|
||||||
|
try {
|
||||||
|
rv.fromBase64(s);
|
||||||
|
return rv;
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public Signature getOfflineSignature() {
|
||||||
|
if (_options == null || _destination == null)
|
||||||
|
return null;
|
||||||
|
String s = _options.getProperty(PROP_OFFLINE_SIGNATURE);
|
||||||
|
if (s == null)
|
||||||
|
return null;
|
||||||
|
Signature rv = new Signature(_destination.getSigningPublicKey().getType());
|
||||||
|
try {
|
||||||
|
rv.fromBase64(s);
|
||||||
|
return rv;
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign the structure using the supplied private key
|
* Sign the structure using the supplied private key
|
||||||
*
|
*
|
||||||
* @param signingKey SigningPrivateKey to sign with
|
* @param signingKey SigningPrivateKey to sign with.
|
||||||
|
* If offline data is set, must be with the transient key.
|
||||||
* @throws DataFormatException
|
* @throws DataFormatException
|
||||||
*/
|
*/
|
||||||
public void signSessionConfig(SigningPrivateKey signingKey) throws DataFormatException {
|
public void signSessionConfig(SigningPrivateKey signingKey) throws DataFormatException {
|
||||||
@ -134,6 +242,8 @@ public class SessionConfig extends DataStructureImpl {
|
|||||||
* Note that this also returns false if the creation date is too far in the
|
* Note that this also returns false if the creation date is too far in the
|
||||||
* past or future. See tooOld() and getCreationDate().
|
* past or future. See tooOld() and getCreationDate().
|
||||||
*
|
*
|
||||||
|
* As of 0.9.38, validates the offline signature if included.
|
||||||
|
*
|
||||||
* @return true only if the signature matches
|
* @return true only if the signature matches
|
||||||
*/
|
*/
|
||||||
public boolean verifySignature() {
|
public boolean verifySignature() {
|
||||||
@ -159,8 +269,35 @@ public class SessionConfig extends DataStructureImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean ok = DSAEngine.getInstance().verifySignature(getSignature(), data,
|
SigningPublicKey spk = getTransientSigningPublicKey();
|
||||||
getDestination().getSigningPublicKey());
|
if (spk != null) {
|
||||||
|
// validate offline sig
|
||||||
|
long expires = getOfflineExpiration();
|
||||||
|
if (expires < _creationDate.getTime())
|
||||||
|
return false;
|
||||||
|
Signature sig = getOfflineSignature();
|
||||||
|
if (sig == null)
|
||||||
|
return false;
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
|
||||||
|
try {
|
||||||
|
DataHelper.writeLong(baos, 4, expires / 1000);
|
||||||
|
DataHelper.writeLong(baos, 2, spk.getType().getCode());
|
||||||
|
spk.writeBytes(baos);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
return false;
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
byte[] odata = baos.toByteArray();
|
||||||
|
boolean ok = DSAEngine.getInstance().verifySignature(sig, odata, 0, odata.length,
|
||||||
|
_destination.getSigningPublicKey());
|
||||||
|
if (!ok)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
spk = getDestination().getSigningPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ok = DSAEngine.getInstance().verifySignature(getSignature(), data, spk);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SessionConfig.class);
|
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SessionConfig.class);
|
||||||
if (log.shouldLog(Log.WARN)) log.warn("DSA signature failed!");
|
if (log.shouldLog(Log.WARN)) log.warn("DSA signature failed!");
|
||||||
|
Reference in New Issue
Block a user