This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 8a031d15aeb86d1ac7a83cddfc353c8b758f7f9c Author: Thomas Wolf <tw...@apache.org> AuthorDate: Mon Jul 12 00:07:14 2021 +0200 Re-instantiate support for chacha20-poly1...@openssh.com This reverts commit 53f06e1f3c1dc3ac65be62e1702ba0fec3e05add. Re-instantiates commit e6e88075ce4fb26190a8b59ec4c0b1da338e2008. --- .../apache/sshd/common/cipher/BaseGCMCipher.java | 2 +- .../apache/sshd/common/cipher/BuiltinCiphers.java | 7 + .../apache/sshd/common/cipher/ChaCha20Cipher.java | 279 +++++++++++++++++++++ .../org/apache/sshd/common/mac/Poly1305Mac.java | 270 ++++++++++++++++++++ .../sshd/common/cipher/ChaCha20CipherTest.java | 59 +++++ .../java/org/apache/sshd/common/BaseBuilder.java | 1 + .../common/session/helpers/AbstractSession.java | 9 +- 7 files changed, 625 insertions(+), 2 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseGCMCipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseGCMCipher.java index d1b3191..501de14 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseGCMCipher.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseGCMCipher.java @@ -74,7 +74,7 @@ public class BaseGCMCipher extends BaseCipher { } /** - * Algorithm parameters for AES/GCM that assumes the IV uses an 8-byte counter field as its most significant bytes. + * Algorithm parameters for AES/GCM that assumes the IV uses an 8-byte counter field as its least significant bytes. */ protected static class CounterGCMParameterSpec extends GCMParameterSpec { protected final byte[] iv; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java index 732901b..dd92139 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java @@ -102,6 +102,12 @@ public enum BuiltinCiphers implements CipherFactory { */ @Deprecated blowfishcbc(Constants.BLOWFISH_CBC, 8, 0, 16, "Blowfish", 128, "Blowfish/CBC/NoPadding", 8), + cc20p1305_openssh(Constants.CC20P1305_OPENSSH, 8, 16, 64, "ChaCha", 256, "ChaCha", 8) { + @Override + public Cipher create() { + return new ChaCha20Cipher(); + } + }, /** * @deprecated * @see <A HREF="https://issues.apache.org/jira/browse/SSHD-1004">SSHD-1004</A> @@ -371,6 +377,7 @@ public enum BuiltinCiphers implements CipherFactory { public static final String ARCFOUR128 = "arcfour128"; public static final String ARCFOUR256 = "arcfour256"; public static final String BLOWFISH_CBC = "blowfish-cbc"; + public static final String CC20P1305_OPENSSH = "chacha20-poly1...@openssh.com"; public static final String TRIPLE_DES_CBC = "3des-cbc"; private Constants() { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ChaCha20Cipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ChaCha20Cipher.java new file mode 100644 index 0000000..9d4023d --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ChaCha20Cipher.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.cipher; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import javax.crypto.AEADBadTagException; + +import org.apache.sshd.common.mac.Mac; +import org.apache.sshd.common.mac.Poly1305Mac; +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; + +/** + * AEAD cipher based on the + * <a href="https://github.com/openbsd/src/blob/master/usr.bin/ssh/PROTOCOL.chacha20poly1305">OpenSSH + * ChaCha20-Poly1305</a> cipher extension. + */ +public class ChaCha20Cipher implements Cipher { + protected final ChaChaEngine headerEngine = new ChaChaEngine(); + protected final ChaChaEngine bodyEngine = new ChaChaEngine(); + protected final Mac mac = new Poly1305Mac(); + protected Mode mode; + + public ChaCha20Cipher() { + // empty + } + + @Override + public String getAlgorithm() { + return "ChaCha20"; + } + + @Override + public void init(Mode mode, byte[] key, byte[] iv) throws Exception { + this.mode = mode; + + bodyEngine.initKey(Arrays.copyOfRange(key, 0, 32)); + bodyEngine.initNonce(iv); + mac.init(bodyEngine.polyKey()); + + headerEngine.initKey(Arrays.copyOfRange(key, 32, 64)); + headerEngine.initNonce(iv); + headerEngine.initCounter(0); + } + + @Override + public void updateAAD(byte[] data, int offset, int length) throws Exception { + ValidateUtils.checkState(mode != null, "Cipher not initialized"); + ValidateUtils.checkTrue(length == 4, "AAD only supported for encrypted packet length"); + + if (mode == Mode.Decrypt) { + mac.update(data, offset, length); + } + + headerEngine.crypt(data, offset, length, data, offset); + + if (mode == Mode.Encrypt) { + mac.update(data, offset, length); + } + } + + @Override + public void update(byte[] input, int inputOffset, int inputLen) throws Exception { + ValidateUtils.checkState(mode != null, "Cipher not initialized"); + + if (mode == Mode.Decrypt) { + mac.update(input, inputOffset, inputLen); + byte[] actual = mac.doFinal(); + if (!Mac.equals(input, inputOffset + inputLen, actual, 0, actual.length)) { + throw new AEADBadTagException("Tag mismatch"); + } + } + + bodyEngine.crypt(input, inputOffset, inputLen, input, inputOffset); + + if (mode == Mode.Encrypt) { + mac.update(input, inputOffset, inputLen); + mac.doFinal(input, inputOffset + inputLen); + } + + headerEngine.advanceNonce(); + headerEngine.initCounter(0); + bodyEngine.advanceNonce(); + mac.init(bodyEngine.polyKey()); + } + + @Override + public String getTransformation() { + return "ChaCha20"; + } + + @Override + public int getIVSize() { + return 8; + } + + @Override + public int getAuthenticationTagSize() { + return 16; + } + + @Override + public int getCipherBlockSize() { + return 8; + } + + @Override + public int getKdfSize() { + return 64; + } + + @Override + public int getKeySize() { + return 256; + } + + protected static class ChaChaEngine { + private static final int BLOCK_BYTES = 64; + private static final int BLOCK_INTS = BLOCK_BYTES / Integer.BYTES; + private static final int KEY_OFFSET = 4; + private static final int KEY_BYTES = 32; + private static final int KEY_INTS = KEY_BYTES / Integer.BYTES; + private static final int COUNTER_OFFSET = 12; + private static final int NONCE_OFFSET = 14; + private static final int NONCE_BYTES = 8; + private static final int NONCE_INTS = NONCE_BYTES / Integer.BYTES; + private static final int[] ENGINE_STATE_HEADER + = unpackSigmaString("expand 32-byte k".getBytes(StandardCharsets.US_ASCII)); + + protected final int[] x = new int[BLOCK_INTS]; + protected final int[] engineState = new int[BLOCK_INTS]; + protected final byte[] nonce = new byte[NONCE_BYTES]; + protected long initialNonce; + + protected ChaChaEngine() { + System.arraycopy(ENGINE_STATE_HEADER, 0, engineState, 0, 4); + } + + protected void initKey(byte[] key) { + unpackIntsLE(key, 0, KEY_INTS, engineState, KEY_OFFSET); + } + + protected void initNonce(byte[] nonce) { + initialNonce = BufferUtils.getLong(nonce, 0, NumberUtils.length(nonce)); + unpackIntsLE(nonce, 0, NONCE_INTS, engineState, NONCE_OFFSET); + System.arraycopy(nonce, 0, this.nonce, 0, NONCE_BYTES); + } + + protected void advanceNonce() { + long counter = BufferUtils.getLong(nonce, 0, NONCE_BYTES) + 1; + ValidateUtils.checkState(counter != initialNonce, "Packet sequence number cannot be reused with the same key"); + BufferUtils.putLong(counter, nonce, 0, NONCE_BYTES); + unpackIntsLE(nonce, 0, NONCE_INTS, engineState, NONCE_OFFSET); + } + + protected void initCounter(long counter) { + engineState[COUNTER_OFFSET] = (int) counter; + engineState[COUNTER_OFFSET + 1] = (int) (counter >>> Integer.SIZE); + } + + // one-shot usage + protected void crypt(byte[] in, int offset, int length, byte[] out, int outOffset) { + while (length > 0) { + System.arraycopy(engineState, 0, x, 0, BLOCK_INTS); + permute(x); + int want = Math.min(BLOCK_BYTES, length); + for (int i = 0, j = 0; i < want; i += Integer.BYTES, j++) { + int keyStream = engineState[j] + x[j]; + int take = Math.min(Integer.BYTES, length); + int input = unpackIntLE(in, offset, take); + int output = keyStream ^ input; + packIntLE(output, out, outOffset, take); + offset += take; + outOffset += take; + length -= take; + } + int lo = ++engineState[COUNTER_OFFSET]; + if (lo == 0) { + // overflow + ++engineState[COUNTER_OFFSET + 1]; + } + } + } + + protected byte[] polyKey() { + byte[] block = new byte[Poly1305Mac.KEY_BYTES]; + initCounter(0); + crypt(block, 0, block.length, block, 0); + initCounter(1); + return block; + } + + protected static void permute(int[] state) { + for (int i = 0; i < 10; i++) { + columnRound(state); + diagonalRound(state); + } + } + + protected static void columnRound(int[] state) { + quarterRound(state, 0, 4, 8, 12); + quarterRound(state, 1, 5, 9, 13); + quarterRound(state, 2, 6, 10, 14); + quarterRound(state, 3, 7, 11, 15); + } + + protected static void diagonalRound(int[] state) { + quarterRound(state, 0, 5, 10, 15); + quarterRound(state, 1, 6, 11, 12); + quarterRound(state, 2, 7, 8, 13); + quarterRound(state, 3, 4, 9, 14); + } + + protected static void quarterRound(int[] state, int a, int b, int c, int d) { + state[a] += state[b]; + state[d] = Integer.rotateLeft(state[d] ^ state[a], 16); + + state[c] += state[d]; + state[b] = Integer.rotateLeft(state[b] ^ state[c], 12); + + state[a] += state[b]; + state[d] = Integer.rotateLeft(state[d] ^ state[a], 8); + + state[c] += state[d]; + state[b] = Integer.rotateLeft(state[b] ^ state[c], 7); + } + + private static int unpackIntLE(byte[] buf, int off) { + return unpackIntLE(buf, off, Integer.BYTES); + } + + private static int unpackIntLE(byte[] buf, int off, int len) { + int ret = 0; + for (int i = 0; i < len; i++) { + ret |= Byte.toUnsignedInt(buf[off + i]) << i * Byte.SIZE; + } + return ret; + } + + private static void unpackIntsLE(byte[] buf, int off, int nrInts, int[] dst, int dstOff) { + for (int i = 0; i < nrInts; i++) { + dst[dstOff++] = unpackIntLE(buf, off); + off += Integer.BYTES; + } + } + + private static int[] unpackSigmaString(byte[] buf) { + int[] values = new int[4]; + unpackIntsLE(buf, 0, 4, values, 0); + return values; + } + + private static void packIntLE(int value, byte[] dst, int off, int len) { + for (int i = 0; i < len; i++) { + dst[off + i] = (byte) (value >>> i * Byte.SIZE); + } + } + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/Poly1305Mac.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/Poly1305Mac.java new file mode 100644 index 0000000..dcb8919 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/Poly1305Mac.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.mac; + +import java.nio.BufferOverflowException; +import java.security.InvalidKeyException; +import java.util.Arrays; + +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; + +/** + * Poly1305 one-time message authentication code. This implementation is derived from the public domain C library + * <a href="https://github.com/floodyberry/poly1305-donna">poly1305-donna</a>. + * + * @see <a href="http://cr.yp.to/mac/poly1305-20050329.pdf">The Poly1305-AES message-authentication code</a> + */ +public class Poly1305Mac implements Mac { + public static final int KEY_BYTES = 32; + private static final int BLOCK_SIZE = 16; + + private int r0; + private int r1; + private int r2; + private int r3; + private int r4; + private int s1; + private int s2; + private int s3; + private int s4; + private int k0; + private int k1; + private int k2; + private int k3; + + private int h0; + private int h1; + private int h2; + private int h3; + private int h4; + private final byte[] currentBlock = new byte[BLOCK_SIZE]; + private int currentBlockOffset; + + public Poly1305Mac() { + // empty + } + + @Override + public String getAlgorithm() { + return "Poly1305"; + } + + @Override + public void init(byte[] key) throws Exception { + if (NumberUtils.length(key) != KEY_BYTES) { + throw new InvalidKeyException("Poly1305 key must be 32 bytes"); + } + + int t0 = unpackIntLE(key, 0); + int t1 = unpackIntLE(key, 4); + int t2 = unpackIntLE(key, 8); + int t3 = unpackIntLE(key, 12); + + // NOTE: The masks perform the key "clamping" implicitly + r0 = t0 & 0x03FFFFFF; + r1 = (t0 >>> 26 | t1 << 6) & 0x03FFFF03; + r2 = (t1 >>> 20 | t2 << 12) & 0x03FFC0FF; + r3 = (t2 >>> 14 | t3 << 18) & 0x03F03FFF; + r4 = t3 >>> 8 & 0x000FFFFF; + + // Precompute multipliers + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + k0 = unpackIntLE(key, 16); + k1 = unpackIntLE(key, 20); + k2 = unpackIntLE(key, 24); + k3 = unpackIntLE(key, 28); + } + + @Override + public void update(byte[] in, int offset, int length) { + while (length > 0) { + if (currentBlockOffset == BLOCK_SIZE) { + processBlock(); + } + + int toCopy = Math.min(length, BLOCK_SIZE - currentBlockOffset); + System.arraycopy(in, offset, currentBlock, currentBlockOffset, toCopy); + offset += toCopy; + length -= toCopy; + currentBlockOffset += toCopy; + } + } + + @Override + public void updateUInt(long value) { + byte[] encoded = new byte[Integer.BYTES]; + BufferUtils.putUInt(value, encoded); + update(encoded); + } + + @Override + public void doFinal(byte[] out, int offset) throws Exception { + if (offset + BLOCK_SIZE > NumberUtils.length(out)) { + throw new BufferOverflowException(); + } + if (currentBlockOffset > 0) { + processBlock(); + } + + h1 += h0 >>> 26; + h0 &= 0x3ffffff; + h2 += h1 >>> 26; + h1 &= 0x3ffffff; + h3 += h2 >>> 26; + h2 &= 0x3ffffff; + h4 += h3 >>> 26; + h3 &= 0x3ffffff; + h0 += (h4 >>> 26) * 5; + h4 &= 0x3ffffff; + h1 += h0 >>> 26; + h0 &= 0x3ffffff; + + int g0 = h0 + 5; + int b = g0 >>> 26; + g0 &= 0x3ffffff; + int g1 = h1 + b; + b = g1 >>> 26; + g1 &= 0x3ffffff; + int g2 = h2 + b; + b = g2 >>> 26; + g2 &= 0x3ffffff; + int g3 = h3 + b; + b = g3 >>> 26; + g3 &= 0x3ffffff; + int g4 = h4 + b - (1 << 26); + + b = (g4 >>> 31) - 1; + int nb = ~b; + h0 = h0 & nb | g0 & b; + h1 = h1 & nb | g1 & b; + h2 = h2 & nb | g2 & b; + h3 = h3 & nb | g3 & b; + h4 = h4 & nb | g4 & b; + + long f0 = Integer.toUnsignedLong(h0 | h1 << 26) + Integer.toUnsignedLong(k0); + long f1 = Integer.toUnsignedLong(h1 >>> 6 | h2 << 20) + Integer.toUnsignedLong(k1); + long f2 = Integer.toUnsignedLong(h2 >>> 12 | h3 << 14) + Integer.toUnsignedLong(k2); + long f3 = Integer.toUnsignedLong(h3 >>> 18 | h4 << 8) + Integer.toUnsignedLong(k3); + + packIntLE((int) f0, out, offset); + f1 += f0 >>> 32; + packIntLE((int) f1, out, offset + 4); + f2 += f1 >>> 32; + packIntLE((int) f2, out, offset + 8); + f3 += f2 >>> 32; + packIntLE((int) f3, out, offset + 12); + + reset(); + } + + private void processBlock() { + if (currentBlockOffset < BLOCK_SIZE) { + // padding + currentBlock[currentBlockOffset] = 1; + for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++) { + currentBlock[i] = 0; + } + } + + long t0 = Integer.toUnsignedLong(unpackIntLE(currentBlock, 0)); + long t1 = Integer.toUnsignedLong(unpackIntLE(currentBlock, 4)); + long t2 = Integer.toUnsignedLong(unpackIntLE(currentBlock, 8)); + long t3 = Integer.toUnsignedLong(unpackIntLE(currentBlock, 12)); + + h0 += t0 & 0x3ffffff; + h1 += (t1 << 32 | t0) >>> 26 & 0x3ffffff; + h2 += (t2 << 32 | t1) >>> 20 & 0x3ffffff; + h3 += (t3 << 32 | t2) >>> 14 & 0x3ffffff; + h4 += t3 >>> 8; + + if (currentBlockOffset == BLOCK_SIZE) { + h4 += 1 << 24; + } + + long tp0 = unsignedProduct(h0, r0) + unsignedProduct(h1, s4) + unsignedProduct(h2, s3) + unsignedProduct(h3, s2) + + unsignedProduct(h4, s1); + long tp1 = unsignedProduct(h0, r1) + unsignedProduct(h1, r0) + unsignedProduct(h2, s4) + unsignedProduct(h3, s3) + + unsignedProduct(h4, s2); + long tp2 = unsignedProduct(h0, r2) + unsignedProduct(h1, r1) + unsignedProduct(h2, r0) + unsignedProduct(h3, s4) + + unsignedProduct(h4, s3); + long tp3 = unsignedProduct(h0, r3) + unsignedProduct(h1, r2) + unsignedProduct(h2, r1) + unsignedProduct(h3, r0) + + unsignedProduct(h4, s4); + long tp4 = unsignedProduct(h0, r4) + unsignedProduct(h1, r3) + unsignedProduct(h2, r2) + unsignedProduct(h3, r1) + + unsignedProduct(h4, r0); + + h0 = (int) tp0 & 0x3ffffff; + tp1 += tp0 >>> 26; + h1 = (int) tp1 & 0x3ffffff; + tp2 += tp1 >>> 26; + h2 = (int) tp2 & 0x3ffffff; + tp3 += tp2 >>> 26; + h3 = (int) tp3 & 0x3ffffff; + tp4 += tp3 >>> 26; + h4 = (int) tp4 & 0x3ffffff; + h0 += (int) (tp4 >>> 26) * 5; + h1 += h0 >>> 26; + h0 &= 0x3ffffff; + + currentBlockOffset = 0; + } + + private void reset() { + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + currentBlockOffset = 0; + Arrays.fill(currentBlock, (byte) 0); + } + + @Override + public int getBlockSize() { + return BLOCK_SIZE; + } + + @Override + public int getDefaultBlockSize() { + return BLOCK_SIZE; + } + + private static int unpackIntLE(byte[] buf, int off) { + int ret = 0; + for (int i = 0; i < Integer.BYTES; i++) { + ret |= Byte.toUnsignedInt(buf[off + i]) << i * Byte.SIZE; + } + return ret; + } + + private static void packIntLE(int value, byte[] dst, int off) { + for (int i = 0; i < Integer.BYTES; i++) { + dst[off + i] = (byte) (value >>> i * Byte.SIZE); + } + } + + private static long unsignedProduct(int i1, int i2) { + return Integer.toUnsignedLong(i1) * Integer.toUnsignedLong(i2); + } +} diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ChaCha20CipherTest.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ChaCha20CipherTest.java new file mode 100644 index 0000000..92b8725 --- /dev/null +++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ChaCha20CipherTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.cipher; + +import java.nio.charset.StandardCharsets; + +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.util.test.JUnitTestSupport; +import org.junit.Test; + +public class ChaCha20CipherTest extends JUnitTestSupport { + public ChaCha20CipherTest() { + super(); + } + + @Test + public void testEncryptDecrypt() throws Exception { + ChaCha20Cipher cipher = new ChaCha20Cipher(); + byte[] key = new byte[cipher.getKdfSize()]; + for (int i = 0; i < key.length; i++) { + key[i] = (byte) (i & 0xff); + } + byte[] iv = new byte[cipher.getIVSize()]; + BufferUtils.putLong(42, iv, 0, iv.length); + byte[] aad = new byte[4]; + byte[] plaintext = getClass().getName().getBytes(StandardCharsets.UTF_8); + BufferUtils.putUInt(plaintext.length, aad); + byte[] buf = new byte[plaintext.length + cipher.getAuthenticationTagSize()]; + System.arraycopy(plaintext, 0, buf, 0, plaintext.length); + cipher.init(Cipher.Mode.Encrypt, key, iv); + cipher.updateAAD(aad); + cipher.update(buf, 0, plaintext.length); + + byte[] ciphertext = buf.clone(); + + cipher.init(Cipher.Mode.Decrypt, key, iv); + cipher.updateAAD(aad); + int length = (int) BufferUtils.getUInt(aad); + cipher.update(ciphertext, 0, length); + assertEquals(getClass().getName(), new String(ciphertext, 0, length, StandardCharsets.UTF_8)); + } +} diff --git a/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java b/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java index 6c24a5c..8db30b7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java @@ -70,6 +70,7 @@ public class BaseBuilder<T extends AbstractFactoryManager, S extends BaseBuilder */ public static final List<BuiltinCiphers> DEFAULT_CIPHERS_PREFERENCE = Collections.unmodifiableList( Arrays.asList( + BuiltinCiphers.cc20p1305_openssh, BuiltinCiphers.aes128ctr, BuiltinCiphers.aes192ctr, BuiltinCiphers.aes256ctr, diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java index ffd0d2c..17ac71d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java @@ -1635,7 +1635,8 @@ public abstract class AbstractSession extends SessionHelper { * * @throws Exception if an error occurs */ - @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance") + // TODO: this method needs refactoring + @SuppressWarnings({ "checkstyle:VariableDeclarationUsageDistance", "checkstyle:ExecutableStatementCount" }) protected void receiveNewKeys() throws Exception { byte[] k = kex.getK(); byte[] h = kex.getH(); @@ -1687,6 +1688,9 @@ public abstract class AbstractSession extends SessionHelper { Cipher s2ccipher = ValidateUtils.checkNotNull( NamedFactory.create(getCipherFactories(), value), "Unknown s2c cipher: %s", value); e_s2c = resizeKey(e_s2c, s2ccipher.getKdfSize(), hash, k, h); + if (s2ccipher.getAlgorithm().startsWith("ChaCha")) { + BufferUtils.putLong(serverSession ? seqo : seqi, iv_s2c, 0, iv_s2c.length); + } s2ccipher.init(serverSession ? Cipher.Mode.Encrypt : Cipher.Mode.Decrypt, e_s2c, iv_s2c); Mac s2cmac; @@ -1712,6 +1716,9 @@ public abstract class AbstractSession extends SessionHelper { Cipher c2scipher = ValidateUtils.checkNotNull( NamedFactory.create(getCipherFactories(), value), "Unknown c2s cipher: %s", value); e_c2s = resizeKey(e_c2s, c2scipher.getKdfSize(), hash, k, h); + if (c2scipher.getAlgorithm().startsWith("ChaCha")) { + BufferUtils.putLong(serverSession ? seqi : seqo, iv_c2s, 0, iv_c2s.length); + } c2scipher.init(serverSession ? Cipher.Mode.Decrypt : Cipher.Mode.Encrypt, e_c2s, iv_c2s); Mac c2smac;