forked from I2P_Developers/i2p.i2p
Ratchet (proposal 144):
- Randomize high two bits of Elligator2 encoding (incompatible change) - Fix NPE in RatchetTagSet.toString() - Use zeros for padding block - Add more debug logging
This commit is contained in:
@ -242,6 +242,8 @@ public final class ECIESAEADEngine {
|
|||||||
state.getLocalKeyPair().setPublicKey(targetPrivateKey.toPublic().getData(), 0);
|
state.getLocalKeyPair().setPublicKey(targetPrivateKey.toPublic().getData(), 0);
|
||||||
state.getLocalKeyPair().setPrivateKey(targetPrivateKey.getData(), 0);
|
state.getLocalKeyPair().setPrivateKey(targetPrivateKey.getData(), 0);
|
||||||
state.start();
|
state.start();
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State before decrypt new session: " + state);
|
||||||
|
|
||||||
// Elg2
|
// Elg2
|
||||||
byte[] tmp = new byte[KEYLEN];
|
byte[] tmp = new byte[KEYLEN];
|
||||||
@ -269,8 +271,10 @@ public final class ECIESAEADEngine {
|
|||||||
|
|
||||||
byte[] bobPK = new byte[KEYLEN];
|
byte[] bobPK = new byte[KEYLEN];
|
||||||
state.getRemotePublicKey().getPublicKey(bobPK, 0);
|
state.getRemotePublicKey().getPublicKey(bobPK, 0);
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug()) {
|
||||||
_log.debug("NS decrypt success from PK " + Base64.encode(bobPK));
|
_log.debug("NS decrypt success from PK " + Base64.encode(bobPK));
|
||||||
|
_log.debug("State after decrypt new session: " + state);
|
||||||
|
}
|
||||||
if (Arrays.equals(bobPK, NULLPK)) {
|
if (Arrays.equals(bobPK, NULLPK)) {
|
||||||
// TODO
|
// TODO
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
@ -351,8 +355,12 @@ public final class ECIESAEADEngine {
|
|||||||
_log.warn("Elg2 decode fail NSR");
|
_log.warn("Elg2 decode fail NSR");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State before decrypt new session reply: " + state);
|
||||||
System.arraycopy(k.getData(), 0, data, TAGLEN, KEYLEN);
|
System.arraycopy(k.getData(), 0, data, TAGLEN, KEYLEN);
|
||||||
state.mixHash(tag, 0, TAGLEN);
|
state.mixHash(tag, 0, TAGLEN);
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
|
||||||
try {
|
try {
|
||||||
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
@ -363,6 +371,8 @@ public final class ECIESAEADEngine {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State after decrypt new session reply: " + state);
|
||||||
|
|
||||||
// split()
|
// split()
|
||||||
byte[] ck = state.getChainingKey();
|
byte[] ck = state.getChainingKey();
|
||||||
@ -622,6 +632,8 @@ public final class ECIESAEADEngine {
|
|||||||
state.getLocalKeyPair().setPublicKey(priv.toPublic().getData(), 0);
|
state.getLocalKeyPair().setPublicKey(priv.toPublic().getData(), 0);
|
||||||
state.getLocalKeyPair().setPrivateKey(priv.getData(), 0);
|
state.getLocalKeyPair().setPrivateKey(priv.getData(), 0);
|
||||||
state.start();
|
state.start();
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State before encrypt new session: " + state);
|
||||||
|
|
||||||
byte[] payload = createPayload(cloves, cloves.getExpiration());
|
byte[] payload = createPayload(cloves, cloves.getExpiration());
|
||||||
|
|
||||||
@ -633,6 +645,8 @@ public final class ECIESAEADEngine {
|
|||||||
_log.warn("Encrypt fail NS", gse);
|
_log.warn("Encrypt fail NS", gse);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State after encrypt new session: " + state);
|
||||||
|
|
||||||
// overwrite eph. key with encoded key
|
// overwrite eph. key with encoded key
|
||||||
DHState eph = state.getLocalEphemeralKeyPair();
|
DHState eph = state.getLocalEphemeralKeyPair();
|
||||||
@ -642,6 +656,8 @@ public final class ECIESAEADEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
eph.getEncodedPublicKey(enc, 0);
|
eph.getEncodedPublicKey(enc, 0);
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("Elligator2 encoded eph. key: " + Base64.encode(enc, 0, 32));
|
||||||
|
|
||||||
// tell the SKM
|
// tell the SKM
|
||||||
keyManager.createSession(target, state);
|
keyManager.createSession(target, state);
|
||||||
@ -669,8 +685,12 @@ public final class ECIESAEADEngine {
|
|||||||
*/
|
*/
|
||||||
private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state,
|
private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state,
|
||||||
RatchetSessionTag currentTag, RatchetSKM keyManager) {
|
RatchetSessionTag currentTag, RatchetSKM keyManager) {
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State before encrypt new session reply: " + state);
|
||||||
byte[] tag = currentTag.getData();
|
byte[] tag = currentTag.getData();
|
||||||
state.mixHash(tag, 0, TAGLEN);
|
state.mixHash(tag, 0, TAGLEN);
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State after mixhash tag before encrypt new session reply: " + state);
|
||||||
|
|
||||||
byte[] payload = createPayload(cloves, 0);
|
byte[] payload = createPayload(cloves, 0);
|
||||||
|
|
||||||
@ -684,6 +704,8 @@ public final class ECIESAEADEngine {
|
|||||||
_log.warn("Encrypt fail NSR part 1", gse);
|
_log.warn("Encrypt fail NSR part 1", gse);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("State after encrypt new session reply: " + state);
|
||||||
|
|
||||||
// overwrite eph. key with encoded key
|
// overwrite eph. key with encoded key
|
||||||
DHState eph = state.getLocalEphemeralKeyPair();
|
DHState eph = state.getLocalEphemeralKeyPair();
|
||||||
@ -847,7 +869,10 @@ public final class ECIESAEADEngine {
|
|||||||
len += block.getTotalLength();
|
len += block.getTotalLength();
|
||||||
}
|
}
|
||||||
int padlen = 1 + _context.random().nextInt(MAXPAD);
|
int padlen = 1 + _context.random().nextInt(MAXPAD);
|
||||||
Block block = new PaddingBlock(_context, padlen);
|
// random data
|
||||||
|
//Block block = new PaddingBlock(_context, padlen);
|
||||||
|
// zeros
|
||||||
|
Block block = new PaddingBlock(padlen);
|
||||||
blocks.add(block);
|
blocks.add(block);
|
||||||
len += block.getTotalLength();
|
len += block.getTotalLength();
|
||||||
byte[] payload = new byte[len];
|
byte[] payload = new byte[len];
|
||||||
|
@ -82,21 +82,25 @@ class Elligator2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From javascript version documentation:
|
* Use for on-the-wire. Don't use for unit tests as output will be randomized
|
||||||
*
|
* based on the 'alternative' and the high bits.
|
||||||
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
* There are eight possible encodings for any point.
|
||||||
* Which one to return is determined by y coordinate.
|
* Output will look like 256 random bits.
|
||||||
* Since Curve25519 doesn't use y due to optimizations, you should specify a Boolean value
|
|
||||||
* as the second argument of the function.
|
|
||||||
* It should be unpredictable, because it's recoverable from the representative.
|
|
||||||
*
|
*
|
||||||
* @return "representative", little endian or null on failure
|
* @return "representative", little endian or null on failure
|
||||||
*/
|
*/
|
||||||
public byte[] encode(PublicKey point) {
|
public byte[] encode(PublicKey point) {
|
||||||
return encode(point, _context.random().nextBoolean());
|
byte[] random = new byte[1];
|
||||||
|
_context.random().nextBytes(random);
|
||||||
|
byte rand = random[0];
|
||||||
|
return encode(point, (rand & 0x01) == 0, rand);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Use for unit tests. Don't use for on-the-wire; use one-arg version.
|
||||||
|
* Output will look like 254 random bits.
|
||||||
|
* High two bits of rv[31] will be zero.
|
||||||
|
*
|
||||||
* From javascript version documentation:
|
* From javascript version documentation:
|
||||||
*
|
*
|
||||||
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
||||||
@ -107,7 +111,26 @@ class Elligator2 {
|
|||||||
*
|
*
|
||||||
* @return "representative", little endian or null on failure
|
* @return "representative", little endian or null on failure
|
||||||
*/
|
*/
|
||||||
public static byte[] encode(PublicKey point, boolean alternative) {
|
protected static byte[] encode(PublicKey point, boolean alternative) {
|
||||||
|
return encode(point, alternative, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output will look like 254 random bits. High two bits of highBits will be ORed in.
|
||||||
|
*
|
||||||
|
* From javascript version documentation:
|
||||||
|
*
|
||||||
|
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
||||||
|
* Which one to return is determined by y coordinate.
|
||||||
|
* Since Curve25519 doesn't use y due to optimizations, you should specify a Boolean value
|
||||||
|
* as the second argument of the function.
|
||||||
|
* It should be unpredictable, because it's recoverable from the representative.
|
||||||
|
*
|
||||||
|
* @param highBits High two bits will be ORed into rv[31]
|
||||||
|
* @return "representative", little endian or null on failure
|
||||||
|
* @since 0.9.45 to add highBits arg
|
||||||
|
*/
|
||||||
|
private static byte[] encode(PublicKey point, boolean alternative, byte highBits) {
|
||||||
if (DISABLE)
|
if (DISABLE)
|
||||||
return point.getData();
|
return point.getData();
|
||||||
|
|
||||||
@ -152,6 +175,8 @@ class Elligator2 {
|
|||||||
|
|
||||||
// little endian
|
// little endian
|
||||||
byte[] rv = ENCODING.encode(r);
|
byte[] rv = ENCODING.encode(r);
|
||||||
|
// randomize two high bits
|
||||||
|
rv[REPRESENTATIVE_LENGTH - 1] |= highBits & (byte) 0xc0;
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +187,7 @@ class Elligator2 {
|
|||||||
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
||||||
*
|
*
|
||||||
* @param representative the encoded data, little endian, 32 bytes
|
* @param representative the encoded data, little endian, 32 bytes
|
||||||
|
* WILL BE MODIFIED by masking byte 31
|
||||||
* @return x or null on failure
|
* @return x or null on failure
|
||||||
*/
|
*/
|
||||||
public static PublicKey decode(byte[] representative) {
|
public static PublicKey decode(byte[] representative) {
|
||||||
@ -175,7 +201,8 @@ class Elligator2 {
|
|||||||
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
||||||
*
|
*
|
||||||
* @param alternative out parameter, or null if you don't care
|
* @param alternative out parameter, or null if you don't care
|
||||||
* @param representative the encoded data, little endian, 32 bytes
|
* @param representative the encoded data, little endian, 32 bytes;
|
||||||
|
* WILL BE MODIFIED by masking byte 31
|
||||||
* @return x or null on failure
|
* @return x or null on failure
|
||||||
*/
|
*/
|
||||||
public static PublicKey decode(AtomicBoolean alternative, byte[] representative) {
|
public static PublicKey decode(AtomicBoolean alternative, byte[] representative) {
|
||||||
@ -185,6 +212,8 @@ class Elligator2 {
|
|||||||
return new PublicKey(EncType.ECIES_X25519, representative);
|
return new PublicKey(EncType.ECIES_X25519, representative);
|
||||||
|
|
||||||
// r
|
// r
|
||||||
|
// Mask out two high bits, to get valid 254 bits.
|
||||||
|
representative[REPRESENTATIVE_LENGTH - 1] &= (byte) 0x3f;
|
||||||
BigInteger r = ENCODING.toBigInteger(representative);
|
BigInteger r = ENCODING.toBigInteger(representative);
|
||||||
|
|
||||||
// If r >= (p - 1) / 2
|
// If r >= (p - 1) / 2
|
||||||
@ -313,6 +342,7 @@ class Elligator2 {
|
|||||||
//00000010 d8 fa ec 68 e5 e6 7e f4 5e bb 82 ee ba 52 60 4f |...h..~.^....R`O|
|
//00000010 d8 fa ec 68 e5 e6 7e f4 5e bb 82 ee ba 52 60 4f |...h..~.^....R`O|
|
||||||
|
|
||||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
|
Elligator2 elg2 = new Elligator2(ctx);
|
||||||
X25519KeyFactory xkf = new X25519KeyFactory(ctx);
|
X25519KeyFactory xkf = new X25519KeyFactory(ctx);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
PublicKey pub;
|
PublicKey pub;
|
||||||
@ -322,9 +352,10 @@ class Elligator2 {
|
|||||||
System.out.println("Trying encode " + ++j);
|
System.out.println("Trying encode " + ++j);
|
||||||
KeyPair kp = xkf.getKeys();
|
KeyPair kp = xkf.getKeys();
|
||||||
pub = kp.getPublic();
|
pub = kp.getPublic();
|
||||||
enc = encode(pub, ctx.random().nextBoolean());
|
enc = elg2.encode(pub);
|
||||||
} while (enc == null);
|
} while (enc == null);
|
||||||
PublicKey pub2 = decode(null, enc);
|
System.out.println("Encoded:\n" + HexDump.dump(enc));
|
||||||
|
PublicKey pub2 = decode(enc);
|
||||||
if (pub2 == null) {
|
if (pub2 == null) {
|
||||||
System.out.println("Decode FAIL");
|
System.out.println("Decode FAIL");
|
||||||
continue;
|
continue;
|
||||||
@ -336,6 +367,20 @@ class Elligator2 {
|
|||||||
System.out.println("calc: " + pub2.toBase64());
|
System.out.println("calc: " + pub2.toBase64());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.out.println("Random decode test");
|
||||||
|
byte[] enc = new byte[32];
|
||||||
|
int fails = 0;
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ctx.random().nextBytes(enc);
|
||||||
|
pk = decode(enc);
|
||||||
|
if (pk == null)
|
||||||
|
fails++;
|
||||||
|
}
|
||||||
|
if (fails > 0)
|
||||||
|
System.out.println("FAIL decode " + fails + " / 1000");
|
||||||
|
else
|
||||||
|
System.out.println("PASS");
|
||||||
}
|
}
|
||||||
****/
|
****/
|
||||||
}
|
}
|
||||||
|
@ -439,6 +439,8 @@ class RatchetTagSet implements TagSetHandle {
|
|||||||
for (int i = 0; i < sz; i++) {
|
for (int i = 0; i < sz; i++) {
|
||||||
int n = _sessionTags.keyAt(i);
|
int n = _sessionTags.keyAt(i);
|
||||||
RatchetSessionTag tag = _sessionTags.valueAt(i);
|
RatchetSessionTag tag = _sessionTags.valueAt(i);
|
||||||
|
if (tag == null)
|
||||||
|
continue;
|
||||||
buf.append("\n " + n + '\t' + Base64.encode(tag.getData()));
|
buf.append("\n " + n + '\t' + Base64.encode(tag.getData()));
|
||||||
if (_sessionKeys != null) {
|
if (_sessionKeys != null) {
|
||||||
byte[] key = _sessionKeys.get(n);
|
byte[] key = _sessionKeys.get(n);
|
||||||
|
Reference in New Issue
Block a user