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;

Reply via email to