diff --git a/core/java/src/net/i2p/crypto/ChaCha20.java b/core/java/src/net/i2p/crypto/ChaCha20.java index 37db1eeb9b..9c368ad5ac 100644 --- a/core/java/src/net/i2p/crypto/ChaCha20.java +++ b/core/java/src/net/i2p/crypto/ChaCha20.java @@ -47,6 +47,19 @@ public final class ChaCha20 { public static void encrypt(byte[] key, byte[] iv, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) { + encrypt(key, iv, 0, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + + /** + * Encrypt from plaintext to ciphertext + * + * @param key first 32 bytes used as the key + * @param iv first 12 bytes starting at ivOffset used as the iv + * @since 0.9.54 + */ + public static void encrypt(byte[] key, byte[] iv, int ivOffset, + byte[] plaintext, int plaintextOffset, + byte[] ciphertext, int ciphertextOffset, int length) { int[] input = new int[16]; int[] output = new int[16]; ChaChaCore.initKey256(input, key, 0); @@ -61,9 +74,9 @@ public final class ChaCha20 { // bits. //ChaChaCore.initIV(input, iv, counter); //ChaChaCore.initIV(input, iv[4:11], iv[0:3]); - input[13] = (int) DataHelper.fromLongLE(iv, 0, 4); - input[14] = (int) DataHelper.fromLongLE(iv, 4, 4); - input[15] = (int) DataHelper.fromLongLE(iv, 8, 4); + input[13] = (int) DataHelper.fromLongLE(iv, ivOffset, 4); + input[14] = (int) DataHelper.fromLongLE(iv, ivOffset + 4, 4); + input[15] = (int) DataHelper.fromLongLE(iv, ivOffset + 8, 4); //System.out.println("initIV"); //dumpBlock(input); ChaChaCore.hash(output, input); @@ -96,7 +109,21 @@ public final class ChaCha20 { byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) { // it's symmetric! - encrypt(key, iv, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + encrypt(key, iv, 0, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + + /** + * Encrypt from ciphertext to plaintext + * + * @param key first 32 bytes used as the key + * @param iv first 12 bytes starting at ivOffset used as the iv + * @since 0.9.54 + */ + public static void decrypt(byte[] key, byte[] iv, int ivOffset, + byte[] ciphertext, int ciphertextOffset, + byte[] plaintext, int plaintextOffset, int length) { + // it's symmetric! + encrypt(key, iv, ivOffset, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); } /**** diff --git a/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java b/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java index 7034b7b341..38923d3ae6 100644 --- a/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java +++ b/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java @@ -114,10 +114,14 @@ public class ChaChaPolyCipherState implements CipherState { /** * Set up to encrypt or decrypt the next packet. + * I2P add off/len * * @param ad The associated data for the packet. + * @param off offset + * @param len length + * @since 0.9.54 added off/len */ - private void setup(byte[] ad) + private void setup(byte[] ad, int off, int len) { if (n == -1L) throw new IllegalStateException("Nonce has wrapped around"); @@ -127,7 +131,7 @@ public class ChaChaPolyCipherState implements CipherState { ChaChaCore.xorBlock(polyKey, 0, polyKey, 0, 32, output); poly.reset(polyKey, 0); if (ad != null) { - poly.update(ad, 0, ad.length); + poly.update(ad, off, len); poly.pad(); } if (++(input[12]) == 0) @@ -155,14 +159,16 @@ public class ChaChaPolyCipherState implements CipherState { /** * Finishes up the authentication tag for a packet. - * - * @param ad The associated data. + * I2P changed ad to adLength; ad data not used here + * + * @param adLength The length of the associated data, 0 if none. * @param length The length of the plaintext data. + * @since 0.9.54 changed ad to adLength */ - private void finish(byte[] ad, int length) + private void finish(int adLength, int length) { poly.pad(); - putLittleEndian64(polyKey, 0, ad != null ? ad.length : 0); + putLittleEndian64(polyKey, 0, adLength); putLittleEndian64(polyKey, 8, length); poly.update(polyKey, 0, 16); poly.finish(polyKey, 0); @@ -195,7 +201,18 @@ public class ChaChaPolyCipherState implements CipherState { @Override public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, - byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException { + byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException { + return encryptWithAd(ad, 0, ad != null ? ad.length : 0, plaintext, plaintextOffset, + ciphertext, ciphertextOffset, length); + } + + /** + * I2P + * @since 0.9.54 + */ + @Override + public int encryptWithAd(byte[] ad, int adOffset, int adLength, byte[] plaintext, int plaintextOffset, + byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException { int space; if (ciphertextOffset > ciphertext.length) space = 0; @@ -211,18 +228,30 @@ public class ChaChaPolyCipherState implements CipherState { } if (space < 16 || length > (space - 16)) throw new ShortBufferException(); - setup(ad); + setup(ad, adOffset, adLength); encrypt(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); poly.update(ciphertext, ciphertextOffset, length); - finish(ad, length); + finish(adLength, length); System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16); return length + 16; } @Override public int decryptWithAd(byte[] ad, byte[] ciphertext, - int ciphertextOffset, byte[] plaintext, int plaintextOffset, - int length) throws ShortBufferException, BadPaddingException { + int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { + return decryptWithAd(ad, 0, ad != null ? ad.length : 0, ciphertext, ciphertextOffset, + plaintext, plaintextOffset, length); + } + + /** + * I2P + * @since 0.9.54 + */ + @Override + public int decryptWithAd(byte[] ad, int adOffset, int adLength, byte[] ciphertext, + int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { int space; if (ciphertextOffset > ciphertext.length) space = 0; @@ -247,9 +276,9 @@ public class ChaChaPolyCipherState implements CipherState { int dataLen = length - 16; if (dataLen > space) throw new ShortBufferException(); - setup(ad); + setup(ad, adOffset, adLength); poly.update(ciphertext, ciphertextOffset, dataLen); - finish(ad, dataLen); + finish(adLength, dataLen); int temp = 0; for (int index = 0; index < 16; ++index) temp |= (polyKey[index] ^ ciphertext[ciphertextOffset + dataLen + index]); diff --git a/router/java/src/com/southernstorm/noise/protocol/CipherState.java b/router/java/src/com/southernstorm/noise/protocol/CipherState.java index 82eaae56c1..d9ead833af 100644 --- a/router/java/src/com/southernstorm/noise/protocol/CipherState.java +++ b/router/java/src/com/southernstorm/noise/protocol/CipherState.java @@ -109,6 +109,13 @@ public interface CipherState extends Destroyable, Cloneable { */ int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException; + /** + * I2P + * @since 0.9.54 + */ + public int encryptWithAd(byte[] ad, int adOffset, int adLength, byte[] plaintext, int plaintextOffset, + byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException; + /** * Decrypts a ciphertext buffer using the cipher and a block of associated data. * @@ -136,6 +143,14 @@ public interface CipherState extends Destroyable, Cloneable { */ int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException; + /** + * I2P + * @since 0.9.54 + */ + public int decryptWithAd(byte[] ad, int adOffset, int adLength, byte[] ciphertext, + int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException; + /** * Creates a new instance of this cipher and initializes it with a key. * diff --git a/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java b/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java index 3dc56bed55..e5312a1a6a 100644 --- a/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java +++ b/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java @@ -128,16 +128,26 @@ public class HandshakeState implements Destroyable, Cloneable { */ private static final int FALLBACK_POSSIBLE = 0x40; + /** NTCP2 */ public static final String protocolName = "Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256"; + /** Ratchet */ public static final String protocolName2 = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"; + /** Tunnels */ public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256"; + /** SSU2 */ + public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256"; private static final String prefix; private final String patternId; + /** NTCP2 */ public static final String PATTERN_ID_XK = "XK"; + /** Ratchet */ public static final String PATTERN_ID_IK = "IK"; + /** Tunnels */ public static final String PATTERN_ID_N = "N"; /** same as N but no post-mixHash needed */ public static final String PATTERN_ID_N_NO_RESPONSE = "N!"; + /** SSU2 */ + public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2"; private static String dh; private static final String cipher; private static final String hash; @@ -182,6 +192,11 @@ public class HandshakeState implements Destroyable, Cloneable { PATTERN_N = Pattern.lookup(id); if (PATTERN_N == null) throw new IllegalArgumentException("Handshake pattern is not recognized"); + // XK-SSU2 + components = protocolName4.split("_"); + id = components[1].substring(0, 2); + if (!PATTERN_ID_XK.equals(id)) + throw new IllegalArgumentException(); } /** @@ -209,6 +224,8 @@ public class HandshakeState implements Destroyable, Cloneable { pattern = PATTERN_N; else if (patternId.equals(PATTERN_ID_N_NO_RESPONSE)) // same as N but no post-mixHash needed pattern = PATTERN_N; + else if (patternId.equals(PATTERN_ID_XK_SSU2)) + pattern = PATTERN_XK; else throw new IllegalArgumentException("Handshake pattern is not recognized"); short flags = pattern[0]; @@ -510,6 +527,18 @@ public class HandshakeState implements Destroyable, Cloneable { /** * Writes a message payload during the handshake. * + * Payload (plaintext) and message (encrypted) may be in the same buffer if the payload + * if offset enough past the message offset to leave room for the + * key(s) and/or MAC. For 32 byte keys and 16 byte MACs, + * if message == payload, payloadOffset must be at least this much + * greater than messageOffset: + * + * XK: Message 1: 32; message 2: 32; message 3: 48 + * + * IK: Message 1: 80; message 2: 48 + * + * N: Message 1: 32 + * * @param message The buffer that will be populated with the * handshake packet to be written to the transport. * @param messageOffset First offset within the message buffer @@ -679,7 +708,10 @@ public class HandshakeState implements Destroyable, Cloneable { /** * Reads a message payload during the handshake. - * + * + * Payload (plaintext) and message (encrypted) may be in the same buffer + * and have the same offset. + * * @param message Buffer containing the incoming handshake * that was read from the transport. * @param messageOffset Offset of the first message byte. diff --git a/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java b/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java index 4e17269656..8d3eb3695e 100644 --- a/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java +++ b/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java @@ -40,15 +40,18 @@ class SymmetricState implements Destroyable, Cloneable { private static final byte[] INIT_CK_XK; private static final byte[] INIT_CK_IK; private static final byte[] INIT_CK_N; + private static final byte[] INIT_CK_XK_SSU2; // precalculated hash of the hash of the Noise name = mixHash(nullPrologue) private static final byte[] INIT_HASH_XK = new byte[32]; private static final byte[] INIT_HASH_IK = new byte[32]; private static final byte[] INIT_HASH_N = new byte[32]; + private static final byte[] INIT_HASH_XK_SSU2 = new byte[32]; static { INIT_CK_XK = initHash(HandshakeState.protocolName); INIT_CK_IK = initHash(HandshakeState.protocolName2); INIT_CK_N = initHash(HandshakeState.protocolName3); + INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4); try { MessageDigest md = Noise.createHash("SHA256"); md.update(INIT_CK_XK, 0, 32); @@ -57,6 +60,8 @@ class SymmetricState implements Destroyable, Cloneable { md.digest(INIT_HASH_IK, 0, 32); md.update(INIT_CK_N, 0, 32); md.digest(INIT_HASH_N, 0, 32); + md.update(INIT_CK_XK_SSU2, 0, 32); + md.digest(INIT_HASH_XK_SSU2, 0, 32); } catch (Exception e) { throw new IllegalStateException(e); } @@ -125,6 +130,9 @@ class SymmetricState implements Destroyable, Cloneable { patternId.equals(HandshakeState.PATTERN_ID_N_NO_RESPONSE)) { initCK = INIT_CK_N; initHash = INIT_HASH_N; + } else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) { + initCK = INIT_CK_XK_SSU2; + initHash = INIT_HASH_XK_SSU2; } else { throw new IllegalArgumentException("Handshake pattern is not recognized"); }