forked from I2P_Developers/i2p.i2p
Router: Quick checks of eph. key MSB before Noise DH
Additional checks on ECIES BRR to catch old/buggy routers Detailed logging of ECIES BRR decrypt fails
This commit is contained in:
@ -18,6 +18,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
@ -113,6 +114,8 @@ public class BuildRequestRecord {
|
||||
/** we show 16 bytes of the peer hash outside the elGamal block */
|
||||
public static final int PEER_SIZE = 16;
|
||||
private static final int DEFAULT_EXPIRATION_SECONDS = 10*60;
|
||||
private static final int EC_LEN = EncType.ECIES_X25519.getPubkeyLen();
|
||||
private static final byte[] NULL_KEY = new byte[EC_LEN];
|
||||
|
||||
/**
|
||||
* @return 222 (ElG) or 464 (ECIES) bytes, non-null
|
||||
@ -379,15 +382,31 @@ public class BuildRequestRecord {
|
||||
*/
|
||||
public BuildRequestRecord(RouterContext ctx, PrivateKey ourKey,
|
||||
EncryptedBuildRecord encryptedRecord) throws DataFormatException {
|
||||
byte[] encrypted = encryptedRecord.getData();
|
||||
byte decrypted[];
|
||||
EncType type = ourKey.getType();
|
||||
if (type == EncType.ELGAMAL_2048) {
|
||||
byte preDecrypt[] = new byte[514];
|
||||
System.arraycopy(encryptedRecord.getData(), PEER_SIZE, preDecrypt, 1, 256);
|
||||
System.arraycopy(encryptedRecord.getData(), PEER_SIZE + 256, preDecrypt, 258, 256);
|
||||
System.arraycopy(encrypted, PEER_SIZE, preDecrypt, 1, 256);
|
||||
System.arraycopy(encrypted, PEER_SIZE + 256, preDecrypt, 258, 256);
|
||||
decrypted = ctx.elGamalEngine().decrypt(preDecrypt, ourKey);
|
||||
_isEC = false;
|
||||
} else if (type == EncType.ECIES_X25519) {
|
||||
// There's several reasons to get bogus-encrypted requests:
|
||||
// very old Java and i2pd routers that don't check the type at all and send ElG,
|
||||
// i2pd treating type 4 like type 1,
|
||||
// and very old i2pd routers like 0.9.32 that have all sorts of bugs.
|
||||
// The following 3 checks weed out about 85% before we get to the DH.
|
||||
|
||||
// fast MSB check for key < 2^255
|
||||
if ((encrypted[PEER_SIZE + EC_LEN - 1] & 0x80) != 0)
|
||||
throw new DataFormatException("Bad PK decrypt fail");
|
||||
// i2pd 0.9.46/47 bug, treating us like type 1
|
||||
if (DataHelper.eq(ourKey.toPublic().getData(), 0, encrypted, PEER_SIZE, EC_LEN))
|
||||
throw new DataFormatException("Our PK decrypt fail");
|
||||
// very old i2pd bug?
|
||||
if (DataHelper.eq(NULL_KEY, 0, encrypted, PEER_SIZE, EC_LEN))
|
||||
throw new DataFormatException("Null PK decrypt fail");
|
||||
HandshakeState state = null;
|
||||
try {
|
||||
KeyFactory kf = TEST ? TESTKF : ctx.commSystem().getXDHFactory();
|
||||
@ -396,13 +415,18 @@ public class BuildRequestRecord {
|
||||
ourKey.toPublic().getData(), 0);
|
||||
state.start();
|
||||
decrypted = new byte[LENGTH_EC];
|
||||
state.readMessage(encryptedRecord.getData(), PEER_SIZE, EncryptedBuildRecord.LENGTH - PEER_SIZE,
|
||||
state.readMessage(encrypted, PEER_SIZE, EncryptedBuildRecord.LENGTH - PEER_SIZE,
|
||||
decrypted, 0);
|
||||
_chachaReplyKey = new SessionKey(state.getChainingKey());
|
||||
_chachaReplyAD = new byte[32];
|
||||
System.arraycopy(state.getHandshakeHash(), 0, _chachaReplyAD, 0, 32);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new DataFormatException("decrypt fail", gse);
|
||||
if (state != null) {
|
||||
Log log = ctx.logManager().getLog(BuildRequestRecord.class);
|
||||
if (log.shouldInfo())
|
||||
log.info("ECIES BRR decrypt failure, state at failure:\n" + state);
|
||||
}
|
||||
throw new DataFormatException("ChaCha decrypt fail", gse);
|
||||
} finally {
|
||||
if (state != null)
|
||||
state.destroy();
|
||||
|
@ -350,18 +350,6 @@ public final class ECIESAEADEngine {
|
||||
*/
|
||||
private CloveSet decryptNewSession(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager)
|
||||
throws DataFormatException {
|
||||
HandshakeState state;
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
|
||||
} 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);
|
||||
|
||||
// Elg2
|
||||
byte[] xx = new byte[KEYLEN];
|
||||
System.arraycopy(data, 0, xx, 0, KEYLEN);
|
||||
@ -373,12 +361,31 @@ public final class ECIESAEADEngine {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Elg2 decode fail NS");
|
||||
data[KEYLEN - 1] = xx31;
|
||||
state.destroy();
|
||||
return null;
|
||||
}
|
||||
// fast MSB check for key < 2^255
|
||||
if ((pk.getData()[KEYLEN - 1] & 0x80) != 0) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Bad PK decode fail NS");
|
||||
data[KEYLEN - 1] = xx31;
|
||||
return null;
|
||||
}
|
||||
|
||||
// rewrite in place, must restore below on failure
|
||||
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
|
||||
|
||||
HandshakeState state;
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
|
||||
} 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 + KEYLEN + MACLEN + MACLEN);
|
||||
byte[] payload = new byte[payloadlen];
|
||||
try {
|
||||
|
@ -672,7 +672,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
}
|
||||
changeState(State.IB_NTCP2_GOT_X);
|
||||
_received = 0;
|
||||
|
||||
// replay check using encrypted key
|
||||
if (!_transport.isHXHIValid(_X)) {
|
||||
_context.statManager().addRateData("ntcp.replayHXxorBIH", 1);
|
||||
@ -680,6 +679,21 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
return;
|
||||
}
|
||||
|
||||
Hash h = _context.routerHash();
|
||||
SessionKey bobHash = new SessionKey(h.getData());
|
||||
// save encrypted data for CBC for msg 2
|
||||
System.arraycopy(_X, KEY_SIZE - IV_SIZE, _prevEncrypted, 0, IV_SIZE);
|
||||
_context.aes().decrypt(_X, 0, _X, 0, bobHash, _transport.getNTCP2StaticIV(), KEY_SIZE);
|
||||
if (DataHelper.eqCT(_X, 0, ZEROKEY, 0, KEY_SIZE)) {
|
||||
fail("Bad msg 1, X = 0");
|
||||
return;
|
||||
}
|
||||
// fast MSB check for key < 2^255
|
||||
if ((_X[KEY_SIZE - 1] & 0x80) != 0) {
|
||||
fail("Bad PK msg 1");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK, HandshakeState.RESPONDER, _transport.getXDHFactory());
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@ -687,15 +701,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
}
|
||||
_handshakeState.getLocalKeyPair().setKeys(_transport.getNTCP2StaticPrivkey(), 0,
|
||||
_transport.getNTCP2StaticPubkey(), 0);
|
||||
Hash h = _context.routerHash();
|
||||
SessionKey bobHash = new SessionKey(h.getData());
|
||||
// save encrypted data for CBC for msg 2
|
||||
System.arraycopy(_X, KEY_SIZE - IV_SIZE, _prevEncrypted, 0, IV_SIZE);
|
||||
_context.aes().decrypt(_X, 0, _X, 0, bobHash, _transport.getNTCP2StaticIV(), KEY_SIZE);
|
||||
if (DataHelper.eqCT(_X, 0, ZEROKEY, 0, KEY_SIZE)) {
|
||||
fail("Bad msg 1, X = 0");
|
||||
return;
|
||||
}
|
||||
byte options[] = new byte[OPTIONS1_SIZE];
|
||||
try {
|
||||
_handshakeState.start();
|
||||
|
Reference in New Issue
Block a user