forked from I2P_Developers/i2p.i2p
Router (proposal 156):
- Change router ECIES SKM to use N pattern, remove Elligator2, to match proposal changes - Allow encrypted messages to ECIES routers - Allow ECIES routers to become floodfill - Add XDH factory to VM comm system for tests
This commit is contained in:
12
history.txt
12
history.txt
@@ -1,6 +1,16 @@
|
||||
2020-12-11 zzz
|
||||
* Router (proposal 156):
|
||||
- Change router ECIES SKM to use N pattern
|
||||
- Allow encrypted messages to ECIES routers
|
||||
- Allow ECIES routers to become floodfill
|
||||
- Add XDH factory to VM comm system for tests
|
||||
|
||||
2020-12-06 zzz
|
||||
* Console, webapps: Move web resources to wars
|
||||
* i2psnark: Initial support for web seeds (WIP)
|
||||
* i2psnark:
|
||||
- Add support for web seeds
|
||||
- Preserve file attribute strings in metainfo
|
||||
* Streaming: Add Retry-After header to throttle response
|
||||
* Util: Change DoH to RFC 8484 protocol
|
||||
|
||||
* 2020-12-01 0.9.48 released
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
|
||||
private PublicKey _ratchetPubKey;
|
||||
private Type _type;
|
||||
|
||||
public static final boolean USE_ECIES_FF = false;
|
||||
public static final boolean USE_ECIES_FF = true;
|
||||
|
||||
//private static volatile long _currentLookupPeriod = 0;
|
||||
//private static volatile int _currentLookupCount = 0;
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 2;
|
||||
public final static long BUILD = 3;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
||||
@@ -64,9 +64,11 @@ public final class ECIESAEADEngine {
|
||||
private static final int BHLEN = RatchetPayload.BLOCK_HEADER_SIZE; // 3
|
||||
private static final int DATETIME_SIZE = BHLEN + 4; // 7
|
||||
private static final int NS_OVERHEAD = KEYLEN + KEYLEN + MACLEN + MACLEN; // 96
|
||||
private static final int NS_N_OVERHEAD = KEYLEN + MACLEN; // 48
|
||||
private static final int NSR_OVERHEAD = TAGLEN + KEYLEN + MACLEN + MACLEN; // 72
|
||||
private static final int ES_OVERHEAD = TAGLEN + MACLEN; // 24
|
||||
private static final int MIN_NS_SIZE = NS_OVERHEAD + DATETIME_SIZE; // 103
|
||||
private static final int MIN_NS_N_SIZE = NS_N_OVERHEAD + DATETIME_SIZE; // 55
|
||||
private static final int MIN_NSR_SIZE = NSR_OVERHEAD; // 72
|
||||
private static final int MIN_ES_SIZE = ES_OVERHEAD; // 24
|
||||
private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE;
|
||||
@@ -312,8 +314,12 @@ public final class ECIESAEADEngine {
|
||||
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
|
||||
RatchetSKM keyManager) throws DataFormatException {
|
||||
CloveSet decrypted;
|
||||
if (data.length >= MIN_NS_SIZE) {
|
||||
decrypted = decryptNewSession(data, targetPrivateKey, keyManager);
|
||||
boolean isRouter = keyManager.getDestination() == null;
|
||||
if (data.length >= MIN_NS_SIZE || (isRouter && data.length >= MIN_NS_N_SIZE)) {
|
||||
if (isRouter)
|
||||
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
|
||||
else
|
||||
decrypted = decryptNewSession(data, targetPrivateKey, keyManager);
|
||||
if (decrypted != null) {
|
||||
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptNewSession");
|
||||
} else {
|
||||
@@ -471,6 +477,113 @@ public final class ECIESAEADEngine {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario 1A: New Session Message Anonymous
|
||||
*
|
||||
* <pre>
|
||||
* - 32 byte ephemeral key (NOT Elligator2)
|
||||
* - payload (7 bytes minimum for DateTime block)
|
||||
* - 16 byte MAC
|
||||
* </pre>
|
||||
*
|
||||
* @param data 55 bytes minimum
|
||||
* @return null if decryption fails
|
||||
* @since 0.9.49
|
||||
*/
|
||||
private CloveSet decryptNewSession_N(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager)
|
||||
throws DataFormatException {
|
||||
// fast MSB check for key < 2^255
|
||||
if ((data[KEYLEN - 1] & 0x80) != 0) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Bad PK N");
|
||||
return null;
|
||||
}
|
||||
|
||||
HandshakeState state;
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_N, HandshakeState.RESPONDER, _context.commSystem().getXDHFactory());
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
state.getLocalKeyPair().setKeys(targetPrivateKey.getData(), 0,
|
||||
targetPrivateKey.toPublic().getData(), 0);
|
||||
state.start();
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before decrypt new session: " + state);
|
||||
|
||||
int payloadlen = data.length - (KEYLEN + MACLEN);
|
||||
byte[] payload = new byte[payloadlen];
|
||||
try {
|
||||
state.readMessage(data, 0, data.length, payload, 0);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
// we'll get this a lot from very old routers
|
||||
// logged at INFO in caller
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Decrypt fail N, state at failure: " + state, gse);
|
||||
state.destroy();
|
||||
return null;
|
||||
}
|
||||
// bloom filter here based on ephemeral key
|
||||
byte[] xx = new byte[KEYLEN];
|
||||
System.arraycopy(data, 0, xx, 0, KEYLEN);
|
||||
PublicKey pk = new PublicKey(EncType.ECIES_X25519, xx);
|
||||
if (keyManager.isDuplicate(pk)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Dup eph. key in IB N: " + pk);
|
||||
state.destroy();
|
||||
return NO_CLOVES;
|
||||
}
|
||||
|
||||
// payload
|
||||
if (payloadlen == 0) {
|
||||
// disallowed, datetime block required
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Zero length payload in N");
|
||||
state.destroy();
|
||||
return NO_CLOVES;
|
||||
}
|
||||
PLCallback pc = new PLCallback();
|
||||
try {
|
||||
int blocks = RatchetPayload.processPayload(_context, pc, payload, 0, payload.length, true);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Processed " + blocks + " blocks in IB N");
|
||||
} catch (DataFormatException e) {
|
||||
state.destroy();
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
state.destroy();
|
||||
throw new DataFormatException("N payload error", e);
|
||||
}
|
||||
|
||||
if (pc.datetime == 0) {
|
||||
// disallowed, datetime block required
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No datetime block in IB N");
|
||||
state.destroy();
|
||||
return NO_CLOVES;
|
||||
}
|
||||
|
||||
if (pc.cloveSet.isEmpty()) {
|
||||
// this is legal ?
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("No garlic block in N payload");
|
||||
state.destroy();
|
||||
return NO_CLOVES;
|
||||
}
|
||||
|
||||
if (_log.shouldDebug()) {
|
||||
_log.debug("N decrypt success");
|
||||
//_log.debug("State after decrypt new session: " + state);
|
||||
}
|
||||
state.destroy();
|
||||
|
||||
int num = pc.cloveSet.size();
|
||||
GarlicClove[] arr = new GarlicClove[num];
|
||||
// msg id and expiration not checked in GarlicMessageReceiver
|
||||
CloveSet rv = new CloveSet(pc.cloveSet.toArray(arr), Certificate.NULL_CERT, 0, pc.datetime);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario 2: New Session Reply Message
|
||||
*
|
||||
@@ -763,8 +876,10 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
if (priv == null) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Encrypting as NS zero-key to " + target);
|
||||
return encryptNewSession(cloves, target, null, null, null, null);
|
||||
_log.debug("Encrypting as N to " + target);
|
||||
// N-in-IK, unused
|
||||
//return encryptNewSession(cloves, target, null, null, null, null);
|
||||
return encryptNewSession(cloves, target);
|
||||
}
|
||||
RatchetEntry re = keyManager.consumeNextAvailableTag(target);
|
||||
if (re == null) {
|
||||
@@ -870,6 +985,49 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* scenario 1A: New Session Message from an anonymous source.
|
||||
*
|
||||
* <pre>
|
||||
* - 32 byte ephemeral key (NOT Elligator2)
|
||||
* - payload
|
||||
* - 16 byte MAC
|
||||
* </pre>
|
||||
*
|
||||
* @return encrypted data or null on failure
|
||||
* @since 0.9.49
|
||||
*/
|
||||
private byte[] encryptNewSession(CloveSet cloves, PublicKey target) {
|
||||
HandshakeState state;
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_N, HandshakeState.INITIATOR, _context.commSystem().getXDHFactory());
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
state.getRemotePublicKey().setPublicKey(target.getData(), 0);
|
||||
state.start();
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before encrypt new session: " + state);
|
||||
|
||||
byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_N_OVERHEAD);
|
||||
|
||||
byte[] enc = new byte[KEYLEN + payload.length + MACLEN];
|
||||
try {
|
||||
state.writeMessage(enc, 0, payload, 0, payload.length);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Encrypt fail N", gse);
|
||||
state.destroy();
|
||||
return null;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Encrypted N: " + enc.length + " bytes, state: " + state);
|
||||
|
||||
state.destroy();
|
||||
return enc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* scenario 2: New Session Reply Message
|
||||
*
|
||||
@@ -1367,7 +1525,7 @@ public final class ECIESAEADEngine {
|
||||
RouterContext ctx = new RouterContext(null, props);
|
||||
ctx.initAll();
|
||||
ECIESAEADEngine e = new ECIESAEADEngine(ctx);
|
||||
RatchetSKM rskm = new RatchetSKM(ctx);
|
||||
RatchetSKM rskm = new RatchetSKM(ctx, new Destination());
|
||||
net.i2p.crypto.KeyPair kp = ctx.keyGenerator().generatePKIKeys(EncType.ECIES_X25519);
|
||||
PublicKey pubKey = kp.getPublic();
|
||||
PrivateKey privKey = kp.getPrivate();
|
||||
@@ -1383,11 +1541,11 @@ public final class ECIESAEADEngine {
|
||||
clove.setData(msg);
|
||||
clove.setCertificate(Certificate.NULL_CERT);
|
||||
clove.setCloveId(0);
|
||||
clove.setExpiration(new java.util.Date(System.currentTimeMillis() + 10000));
|
||||
clove.setExpiration(System.currentTimeMillis() + 10000);
|
||||
clove.setInstructions(net.i2p.data.i2np.DeliveryInstructions.LOCAL);
|
||||
GarlicClove[] arr = new GarlicClove[1];
|
||||
arr[0] = clove;
|
||||
CloveSet cs = new CloveSet(arr, Certificate.NULL_CERT, clove.getCloveId(), clove.getExpiration().getTime());
|
||||
CloveSet cs = new CloveSet(arr, Certificate.NULL_CERT, clove.getCloveId(), clove.getExpiration());
|
||||
|
||||
// IK test
|
||||
byte[] encrypted = e.encrypt(cs, pubKey, dest, privKey2, rskm, null);
|
||||
@@ -1409,6 +1567,7 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
|
||||
// N test
|
||||
rskm = new RatchetSKM(ctx);
|
||||
encrypted = e.encrypt(cs, pubKey);
|
||||
System.out.println("N Encrypted:\n" + net.i2p.util.HexDump.dump(encrypted));
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.crypto.X25519KeyFactory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -29,6 +30,8 @@ import net.i2p.util.Log;
|
||||
public class VMCommSystem extends CommSystemFacade {
|
||||
private final Log _log;
|
||||
private final RouterContext _context;
|
||||
private final X25519KeyFactory _xdhThread;
|
||||
|
||||
/**
|
||||
* Mapping from Hash to VMCommSystem for all routers hooked together
|
||||
*/
|
||||
@@ -47,7 +50,16 @@ public class VMCommSystem extends CommSystemFacade {
|
||||
_context.statManager().createRateStat("transport.sendMessageLarge", "How many messages over 4KB are sent?", "Transport", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("transport.receiveMessageLarge", "How many messages over 4KB are received?", "Transport", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRequiredRateStat("transport.sendProcessingTime", "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
// we do NOT start the thread, all keys will be generated inline
|
||||
_xdhThread = new X25519KeyFactory(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for making X25519 key pairs.
|
||||
* @since 0.9.49 so some tests don't NPE
|
||||
*/
|
||||
@Override
|
||||
public X25519KeyFactory getXDHFactory() { return _xdhThread; }
|
||||
|
||||
public int countActivePeers() { return _commSystemFacades.size() - 1; }
|
||||
|
||||
|
||||
@@ -145,9 +145,6 @@ class FloodfillMonitorJob extends JobImpl {
|
||||
RouterIdentity ident = ri.getIdentity();
|
||||
if (ident.getSigningPublicKey().getType() == SigType.DSA_SHA1)
|
||||
return false;
|
||||
// temp until router ratchet SKM implemented
|
||||
if (ident.getPublicKey().getType() != EncType.ELGAMAL_2048)
|
||||
return false;
|
||||
|
||||
char bw = ri.getBandwidthTier().charAt(0);
|
||||
// Only if class N, O, P, X
|
||||
|
||||
Reference in New Issue
Block a user