I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit 926bce78 authored by zzz's avatar zzz
Browse files

I2CP: Set and validate offline sig in SessionConfig

parent d054c6bc
No related branches found
No related tags found
No related merge requests found
...@@ -95,17 +95,18 @@ class I2CPMessageProducer { ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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!");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment