This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 88f81a47ec899657e38d0b90ac77526f0a68bfa7
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Thu Oct 17 10:26:28 2019 +0300

    [SSHD-949] Session should use cipher block size and not IV size to 
calculate padding
---
 CHANGES.md                                         |   4 +-
 .../apache/sshd/common/cipher/AES192CTRTest.java   |   2 +-
 .../apache/sshd/common/cipher/AES256CBCTest.java   |   2 +-
 .../apache/sshd/common/cipher/ARCFOUR256Test.java  |   2 +-
 .../apache/sshd/common/cipher/BaseCipherTest.java  |  40 +++--
 .../org/apache/sshd/common/io/PacketWriter.java    |  25 +++
 .../common/session/helpers/AbstractSession.java    | 179 +++++++++++++--------
 .../sshd/common/session/helpers/SessionHelper.java |   6 +-
 .../sshd/common/cipher/BuiltinCiphersTest.java     |   3 +-
 .../org/apache/sshd/common/cipher/CipherTest.java  |  24 ++-
 10 files changed, 201 insertions(+), 86 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6ac150d..9193bbf 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -35,7 +35,7 @@ the standard does not specifically specify the behavior 
regarding symbolic links
 `createKeyExchange` method that accepts the session instance through which the 
request is made.
 
 * `Signature` methods accept a `SessionContext` argument representing the 
session context
-of their invocation (if any)
+of their invocation (if any).
 
 * Default MAC(s) list is set according to the 
[ssh_config(5)](https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5)
 order as **first** ones, where the supported MAC(s) that do no appear in it 
come last.
@@ -93,3 +93,5 @@ for the server's identification before sending KEX-INIT 
message.
 
 * [SSHD-948](https://issues.apache.org/jira/browse/SSHD-948) - Do not accept 
password authentication if the session is not encrypted.
 
+* [SSHD-949](https://issues.apache.org/jira/browse/SSHD-949) - Session should 
use cipher block size and not IV size to calculate padding.
+
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
index 6611702..bdd220a 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
@@ -35,7 +35,7 @@ public class AES192CTRTest extends BaseCipherTest {
     @Test
     public void testEncryptDecrypt() throws Exception {
         // for AES 256 bits we need the JCE unlimited strength policy
-        ensureKeySizeSupported(16, 24, "AES", "AES/CTR/NoPadding");
+        ensureFullCipherInformationSupported(BuiltinCiphers.aes192ctr);
         testEncryptDecrypt(BuiltinCiphers.aes192ctr);
     }
 }
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
index dd39fd4..6503b23 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
@@ -35,7 +35,7 @@ public class AES256CBCTest extends BaseCipherTest {
     @Test
     public void testEncryptDecrypt() throws Exception {
         // for AES 256 bits we need the JCE unlimited strength policy
-        ensureKeySizeSupported(16, 32, "AES", "AES/CBC/NoPadding");
+        ensureFullCipherInformationSupported(BuiltinCiphers.aes256cbc);
         testEncryptDecrypt(BuiltinCiphers.aes256cbc);
     }
 }
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
index 5511e0f..e9dddf8 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
@@ -35,7 +35,7 @@ public class ARCFOUR256Test extends BaseCipherTest {
     @Test
     public void testEncryptDecrypt() throws Exception {
         // for RC4 256 bits we need the JCE unlimited strength policy
-        ensureKeySizeSupported(32, "ARCFOUR", "RC4");
+        ensureCipherInformationKeySizeSupported(BuiltinCiphers.arcfour256);
         testEncryptDecrypt(BuiltinCiphers.arcfour256);
     }
 }
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
index 8dc68ea..b503761 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
@@ -43,9 +43,17 @@ public abstract class BaseCipherTest extends 
JUnitTestSupport {
         super();
     }
 
-    protected void ensureKeySizeSupported(int bsize, String algorithm, String 
transformation) throws GeneralSecurityException {
+    protected void ensureCipherInformationKeySizeSupported(CipherInformation 
cipher)
+            throws GeneralSecurityException {
+        ensureKeySizeSupported(
+            cipher.getCipherBlockSize(), cipher.getAlgorithm(), 
cipher.getTransformation());
+    }
+
+    protected void ensureKeySizeSupported(int bsize, String algorithm, String 
transformation)
+            throws GeneralSecurityException {
         try {
-            javax.crypto.Cipher cipher = 
SecurityUtils.getCipher(transformation);
+            javax.crypto.Cipher cipher =
+                SecurityUtils.getCipher(transformation);
             byte[] key = new byte[bsize];
             cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, new 
SecretKeySpec(key, algorithm));
         } catch (GeneralSecurityException e) {
@@ -57,12 +65,23 @@ public abstract class BaseCipherTest extends 
JUnitTestSupport {
         }
     }
 
-    protected void ensureKeySizeSupported(int ivsize, int bsize, String 
algorithm, String transformation) throws GeneralSecurityException {
+    protected void ensureFullCipherInformationSupported(CipherInformation 
cipher)
+            throws GeneralSecurityException {
+        ensureKeySizeSupported(
+            cipher.getIVSize(), cipher.getCipherBlockSize(), 
cipher.getAlgorithm(), cipher.getTransformation());
+    }
+
+    protected void ensureKeySizeSupported(
+            int ivsize, int bsize, String algorithm, String transformation)
+                throws GeneralSecurityException {
         try {
-            javax.crypto.Cipher cipher = 
SecurityUtils.getCipher(transformation);
+            javax.crypto.Cipher cipher =
+                SecurityUtils.getCipher(transformation);
             byte[] key = new byte[bsize];
             byte[] iv = new byte[ivsize];
-            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, new 
SecretKeySpec(key, algorithm), new IvParameterSpec(iv));
+            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE,
+                new SecretKeySpec(key, algorithm),
+                new IvParameterSpec(iv));
         } catch (GeneralSecurityException e) {
             if (e instanceof InvalidKeyException) {
                 Assume.assumeTrue(algorithm + "/" + transformation + "[" + 
bsize + "/" + ivsize + "]", false /* force exception */);
@@ -81,15 +100,16 @@ public abstract class BaseCipherTest extends 
JUnitTestSupport {
         byte[] iv = new byte[ivSize];
         enc.init(Mode.Encrypt, key, iv);
 
-        byte[] expected = facName.getBytes(StandardCharsets.UTF_8);
-        byte[] workBuf = expected.clone();    // need to clone since the 
cipher works in-line
+        String expected = getClass().getName() + "[" + facName + "]";
+        byte[] expBytes = expected.getBytes(StandardCharsets.UTF_8);
+        byte[] workBuf = expBytes.clone();    // need to clone since the 
cipher works in-line
         enc.update(workBuf, 0, workBuf.length);
 
         Cipher dec = factory.create();
         dec.init(Mode.Decrypt, key, iv);
-        byte[] actual = workBuf.clone();
-        dec.update(actual, 0, actual.length);
+        byte[] actBytes = workBuf.clone();  // need to clone since the cipher 
works in-line
+        dec.update(actBytes, 0, actBytes.length);
 
-        assertArrayEquals(facName, expected, actual);
+        assertArrayEquals(facName, expBytes, actBytes);
     }
 }
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/io/PacketWriter.java 
b/sshd-core/src/main/java/org/apache/sshd/common/io/PacketWriter.java
index 59f66b0..1be1c88 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/io/PacketWriter.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/io/PacketWriter.java
@@ -46,11 +46,36 @@ public interface PacketWriter extends Channel {
      * @return The required padding length
      */
     static int calculatePadLength(int len, int blockSize, boolean etmMode) {
+        /*
+         * Note: according to RFC-4253 section 6:
+         *
+         *    The minimum size of a packet is 16 (or the cipher block size,
+         *     whichever is larger) bytes (plus 'mac').
+         *
+         * Since all out ciphers, MAC(s), etc. have a block size > 8 then
+         * the minimum size of the packet will be at least 16 due to the
+         * padding at the very least - so even packets that contain an opcode
+         * with no arguments will be above this value. This avoids an 
un-necessary
+         * call to Math.max(len, 16) for each and every packet
+         */
+
         len++;  // the pad length
         if (!etmMode) {
             len += Integer.BYTES;
         }
 
+        /*
+         * Note: according to RFC-4253 section 6:
+         *
+         *      Note that the length of the concatenation of 'packet_length',
+         *      'padding_length', 'payload', and 'random padding' MUST be a 
multiple
+         *      of the cipher block size or 8, whichever is larger.
+         *
+         * However, we currently do not have ciphers with a block size of less 
than
+         * 8 so we do not take this into account in order to accelerate the 
calculation
+         * and avoiding an un-necessary call to Math.max(blockSize, 8) for 
each and every
+         * packet.
+         */
         int pad = (-len) & (blockSize - 1);
         if (pad < blockSize) {
             pad += blockSize;
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 241162e..fcf2c7d 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
@@ -161,6 +161,8 @@ public abstract class AbstractSession extends SessionHelper 
{
     protected int inCipherSize = 8;
     protected Mac outMac;
     protected Mac inMac;
+    protected int outMacSize;
+    protected int inMacSize;
     protected byte[] inMacResult;
     protected Compression outCompression;
     protected Compression inCompression;
@@ -197,16 +199,18 @@ public abstract class AbstractSession extends 
SessionHelper {
     protected long ignorePacketsFrequency = 
FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY;
     protected int ignorePacketsVariance = 
FactoryManager.DEFAULT_IGNORE_MESSAGE_VARIANCE;
 
-    protected final AtomicLong maxRekeyBlocks = new 
AtomicLong(FactoryManager.DEFAULT_REKEY_BYTES_LIMIT / 16);
-    protected final AtomicLong ignorePacketsCount = new 
AtomicLong(FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
+    protected final AtomicLong maxRekeyBlocks =
+        new AtomicLong(FactoryManager.DEFAULT_REKEY_BYTES_LIMIT / 16);
+    protected final AtomicLong ignorePacketsCount =
+        new AtomicLong(FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
 
     /**
      * Used to wait for global requests result synchronous wait
      */
     private final AtomicReference<Object> requestResult = new 
AtomicReference<>();
 
-    private byte[] clientKexData;    // the payload of the client's 
SSH_MSG_KEXINIT
-    private byte[] serverKexData; // the payload of the factoryManager's 
SSH_MSG_KEXINIT
+    private byte[] clientKexData;   // the payload of the client's 
SSH_MSG_KEXINIT
+    private byte[] serverKexData;   // the payload of the server's 
SSH_MSG_KEXINIT
 
     /**
      * Create a new session.
@@ -215,7 +219,8 @@ public abstract class AbstractSession extends SessionHelper 
{
      * @param factoryManager the factory manager
      * @param ioSession      the underlying I/O session
      */
-    protected AbstractSession(boolean serverSession, FactoryManager 
factoryManager, IoSession ioSession) {
+    protected AbstractSession(
+            boolean serverSession, FactoryManager factoryManager, IoSession 
ioSession) {
         super(serverSession, factoryManager, ioSession);
 
         this.decoderBuffer = new SessionWorkBuffer(this);
@@ -223,15 +228,20 @@ public abstract class AbstractSession extends 
SessionHelper {
         attachSession(ioSession, this);
 
         Factory<Random> factory =
-            ValidateUtils.checkNotNull(factoryManager.getRandomFactory(), "No 
random factory for %s", ioSession);
-        random = ValidateUtils.checkNotNull(factory.create(), "No randomizer 
instance for %s", ioSession);
+            ValidateUtils.checkNotNull(
+                factoryManager.getRandomFactory(), "No random factory for %s", 
ioSession);
+        random = ValidateUtils.checkNotNull(
+            factory.create(), "No randomizer instance for %s", ioSession);
 
         refreshConfiguration();
 
         ClassLoader loader = getClass().getClassLoader();
-        sessionListenerProxy = 
EventListenerUtils.proxyWrapper(SessionListener.class, loader, 
sessionListeners);
-        channelListenerProxy = 
EventListenerUtils.proxyWrapper(ChannelListener.class, loader, 
channelListeners);
-        tunnelListenerProxy = 
EventListenerUtils.proxyWrapper(PortForwardingEventListener.class, loader, 
tunnelListeners);
+        sessionListenerProxy = EventListenerUtils.proxyWrapper(
+            SessionListener.class, loader, sessionListeners);
+        channelListenerProxy = EventListenerUtils.proxyWrapper(
+            ChannelListener.class, loader, channelListeners);
+        tunnelListenerProxy = EventListenerUtils.proxyWrapper(
+            PortForwardingEventListener.class, loader, tunnelListeners);
 
         try {
             signalSessionEstablished(ioSession);
@@ -345,19 +355,24 @@ public abstract class AbstractSession extends 
SessionHelper {
     protected void refreshConfiguration() {
         synchronized (random) {
             // re-keying configuration
-            maxRekeyBytes = 
this.getLongProperty(FactoryManager.REKEY_BYTES_LIMIT, maxRekeyBytes);
-            maxRekeyInterval = 
this.getLongProperty(FactoryManager.REKEY_TIME_LIMIT, maxRekeyInterval);
-            maxRekyPackets = 
this.getLongProperty(FactoryManager.REKEY_PACKETS_LIMIT, maxRekyPackets);
+            maxRekeyBytes = getLongProperty(FactoryManager.REKEY_BYTES_LIMIT, 
maxRekeyBytes);
+            maxRekeyInterval = 
getLongProperty(FactoryManager.REKEY_TIME_LIMIT, maxRekeyInterval);
+            maxRekyPackets = 
getLongProperty(FactoryManager.REKEY_PACKETS_LIMIT, maxRekyPackets);
 
             // intermittent SSH_MSG_IGNORE stream padding
-            ignorePacketDataLength = 
this.getIntProperty(FactoryManager.IGNORE_MESSAGE_SIZE, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_SIZE);
-            ignorePacketsFrequency = 
this.getLongProperty(FactoryManager.IGNORE_MESSAGE_FREQUENCY, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
-            ignorePacketsVariance = 
this.getIntProperty(FactoryManager.IGNORE_MESSAGE_VARIANCE, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_VARIANCE);
+            ignorePacketDataLength = getIntProperty(
+                FactoryManager.IGNORE_MESSAGE_SIZE, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_SIZE);
+            ignorePacketsFrequency = getLongProperty(
+                FactoryManager.IGNORE_MESSAGE_FREQUENCY, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
+            ignorePacketsVariance = getIntProperty(
+                FactoryManager.IGNORE_MESSAGE_VARIANCE, 
FactoryManager.DEFAULT_IGNORE_MESSAGE_VARIANCE);
             if (ignorePacketsVariance >= ignorePacketsFrequency) {
                 ignorePacketsVariance = 0;
             }
 
-            ignorePacketsCount.set(calculateNextIgnorePacketCount(random, 
ignorePacketsFrequency, ignorePacketsVariance));
+            long countValue = calculateNextIgnorePacketCount(
+                random, ignorePacketsFrequency, ignorePacketsVariance);
+            ignorePacketsCount.set(countValue);
         }
     }
 
@@ -399,7 +414,8 @@ public abstract class AbstractSession extends SessionHelper 
{
     protected void doHandleMessage(Buffer buffer) throws Exception {
         int cmd = buffer.getUByte();
         if (log.isTraceEnabled()) {
-            log.trace("doHandleMessage({}) process {}", this, 
SshConstants.getCommandMessageName(cmd));
+            log.trace("doHandleMessage({}) process {}",
+                this, SshConstants.getCommandMessageName(cmd));
         }
 
         switch (cmd) {
@@ -541,7 +557,8 @@ public abstract class AbstractSession extends SessionHelper 
{
          *          + As the next packet following the server's first 
SSH_MSG_NEWKEYS.
          */
         KexExtensionHandler extHandler = getKexExtensionHandler();
-        if ((extHandler == null) || 
(!extHandler.isKexExtensionsAvailable(this, AvailabilityPhase.NEWKEYS))) {
+        if ((extHandler == null)
+                || (!extHandler.isKexExtensionsAvailable(this, 
AvailabilityPhase.NEWKEYS))) {
             return future;
         }
 
@@ -555,14 +572,16 @@ public abstract class AbstractSession extends 
SessionHelper {
         boolean debugEnabled = log.isDebugEnabled();
         if (kex.next(cmd, buffer)) {
             if (debugEnabled) {
-                log.debug("handleKexMessage({})[{}] KEX processing complete 
after cmd={}", this, kex.getName(), cmd);
+                log.debug("handleKexMessage({})[{}] KEX processing complete 
after cmd={}",
+                    this, kex.getName(), cmd);
             }
             checkKeys();
             sendNewKeys();
             kexState.set(KexState.KEYS);
         } else {
             if (debugEnabled) {
-                log.debug("handleKexMessage({})[{}] more KEX packets expected 
after cmd={}", this, kex.getName(), cmd);
+                log.debug("handleKexMessage({})[{}] more KEX packets expected 
after cmd={}",
+                    this, kex.getName(), cmd);
             }
         }
     }
@@ -620,7 +639,8 @@ public abstract class AbstractSession extends SessionHelper 
{
             log.debug("handleServiceRequest({}) Accepted service {}", this, 
serviceName);
         }
 
-        Buffer response = createBuffer(SshConstants.SSH_MSG_SERVICE_ACCEPT, 
Byte.SIZE + GenericUtils.length(serviceName));
+        Buffer response = createBuffer(
+            SshConstants.SSH_MSG_SERVICE_ACCEPT, Byte.SIZE + 
GenericUtils.length(serviceName));
         response.putString(serviceName);
         writePacket(response);
         return true;
@@ -747,8 +767,9 @@ public abstract class AbstractSession extends SessionHelper 
{
     protected void validateKexState(int cmd, KexState expected) {
         KexState actual = kexState.get();
         if (!expected.equals(actual)) {
-            throw new IllegalStateException("Received KEX command=" + 
SshConstants.getCommandMessageName(cmd)
-                  + " while in state=" + actual + " instead of " + expected);
+            throw new IllegalStateException(
+                "Received KEX command=" + 
SshConstants.getCommandMessageName(cmd)
+              + " while in state=" + actual + " instead of " + expected);
         }
     }
 
@@ -803,7 +824,8 @@ public abstract class AbstractSession extends SessionHelper 
{
     @Override
     public <T extends Service> T getService(Class<T> clazz) {
         Collection<? extends Service> registeredServices = getServices();
-        ValidateUtils.checkState(GenericUtils.isNotEmpty(registeredServices), 
"No registered services to look for %s", clazz.getSimpleName());
+        ValidateUtils.checkState(GenericUtils.isNotEmpty(registeredServices),
+            "No registered services to look for %s", clazz.getSimpleName());
 
         for (Service s : registeredServices) {
             if (clazz.isInstance(s)) {
@@ -822,9 +844,10 @@ public abstract class AbstractSession extends 
SessionHelper {
             int cmd = bufData[buffer.rpos()] & 0xFF;
             if (cmd > SshConstants.SSH_MSG_KEX_LAST) {
                 String cmdName = SshConstants.getCommandMessageName(cmd);
+                boolean debugEnabled = log.isDebugEnabled();
                 synchronized (pendingPackets) {
                     if (!KexState.DONE.equals(kexState.get())) {
-                        if (pendingPackets.isEmpty()) {
+                        if (pendingPackets.isEmpty() && debugEnabled) {
                             log.debug("writePacket({})[{}] Start flagging 
packets as pending until key exchange is done", this, cmdName);
                         }
                         PendingWriteFuture future = new 
PendingWriteFuture(cmdName, buffer);
@@ -898,7 +921,9 @@ public abstract class AbstractSession extends SessionHelper 
{
     }
 
     protected int resolveIgnoreBufferDataLength() {
-        if ((ignorePacketDataLength <= 0) || (ignorePacketsFrequency <= 0L) || 
(ignorePacketsVariance < 0)) {
+        if ((ignorePacketDataLength <= 0)
+                || (ignorePacketsFrequency <= 0L)
+                || (ignorePacketsVariance < 0)) {
             return 0;
         }
 
@@ -908,7 +933,8 @@ public abstract class AbstractSession extends SessionHelper 
{
         }
 
         synchronized (random) {
-            count = calculateNextIgnorePacketCount(random, 
ignorePacketsFrequency, ignorePacketsVariance);
+            count = calculateNextIgnorePacketCount(
+                random, ignorePacketsFrequency, ignorePacketsVariance);
             ignorePacketsCount.set(count);
             return ignorePacketDataLength + 
random.random(ignorePacketDataLength);
         }
@@ -990,16 +1016,11 @@ public abstract class AbstractSession extends 
SessionHelper {
         // Since the caller claims to know how many bytes they will need
         // increase their request to account for our headers/footers if
         // they actually send exactly this amount.
-        //
-        int bsize = outCipherSize;
-        len += SshConstants.SSH_PACKET_HEADER_LEN;
-        int pad = (-len) & (bsize - 1);
-        if (pad < bsize) {
-            pad += bsize;
-        }
-        len = len + pad - 4;
+        boolean etmMode = (outMac == null) ? false : outMac.isEncryptThenMac();
+        int pad = PacketWriter.calculatePadLength(len, outCipherSize, etmMode);
+        len = SshConstants.SSH_PACKET_HEADER_LEN + len + pad + Byte.BYTES /* 
the pad length byte */;
         if (outMac != null) {
-            len += outMac.getBlockSize();
+            len += outMacSize;
         }
 
         return prepareBuffer(cmd, new ByteArrayBuffer(new byte[len + 
Byte.SIZE], false));
@@ -1026,8 +1047,10 @@ public abstract class AbstractSession extends 
SessionHelper {
      */
     protected <B extends Buffer> B validateTargetBuffer(int cmd, B buffer) {
         ValidateUtils.checkNotNull(buffer, "No target buffer to examine for 
command=%d", cmd);
-        ValidateUtils.checkTrue(buffer != decoderBuffer, "Not allowed to use 
the internal decoder buffer for command=%d", cmd);
-        ValidateUtils.checkTrue(buffer != uncompressBuffer, "Not allowed to 
use the internal uncompress buffer for command=%d", cmd);
+        ValidateUtils.checkTrue(
+            buffer != decoderBuffer, "Not allowed to use the internal decoder 
buffer for command=%d", cmd);
+        ValidateUtils.checkTrue(
+            buffer != uncompressBuffer, "Not allowed to use the internal 
uncompress buffer for command=%d", cmd);
         return buffer;
     }
 
@@ -1068,7 +1091,8 @@ public abstract class AbstractSession extends 
SessionHelper {
             // Debug log the packet
             boolean traceEnabled = log.isTraceEnabled();
             if (traceEnabled) {
-                buffer.dumpHex(getSimplifiedLogger(), Level.FINEST, "encode(" 
+ this + ") packet #" + seqo, this);
+                buffer.dumpHex(getSimplifiedLogger(), Level.FINEST,
+                    "encode(" + this + ") packet #" + seqo, this);
             }
 
             // Compress the packet if needed
@@ -1116,9 +1140,11 @@ public abstract class AbstractSession extends 
SessionHelper {
 
             // Increment packet id
             seqo = (seqo + 1L) & 0x0ffffffffL;
-            // Update stats
+
+            // Update counters used to track re-keying
             outPacketsCount.incrementAndGet();
             outBytesCount.addAndGet(len);
+
             // Make buffer ready to be read
             buffer.rpos(off);
             return buffer;
@@ -1134,10 +1160,9 @@ public abstract class AbstractSession extends 
SessionHelper {
             return;
         }
 
-        int macSize = outMac.getBlockSize();
         int l = buf.wpos();
         // ensure enough room for MAC in outgoing buffer
-        buf.wpos(l + macSize);
+        buf.wpos(l + outMacSize);
         // Include sequence number
         outMac.updateUInt(seqo);
         // Include the length field in the MAC calculation
@@ -1152,7 +1177,7 @@ public abstract class AbstractSession extends 
SessionHelper {
         }
         outCipher.update(buf.array(), offset, len);
 
-        int blocksCount = len / outCipher.getCipherBlockSize();
+        int blocksCount = len / outCipherSize;
         outBlocksCount.addAndGet(Math.max(1, blocksCount));
     }
 
@@ -1169,10 +1194,19 @@ public abstract class AbstractSession extends 
SessionHelper {
             if (decoderState == 0) {
                 // The read position should always be 0 at this point because 
we have compacted this buffer
                 assert decoderBuffer.rpos() == 0;
+                /*
+                 * Note: according to RFC-4253 section 6:
+                 *
+                 *      Implementations SHOULD decrypt the length after 
receiving
+                 *      the first 8 (or cipher block size  whichever is 
larger) bytes
+                 *
+                 * However, we currently do not have ciphers with a block size 
of less than
+                 * 8 we avoid un-necessary Math.max(minBufLen, 8) for each and 
every packet
+                 */
                 int minBufLen = etmMode ? Integer.BYTES : inCipherSize;
                 // If we have received enough bytes, start processing those
                 if (decoderBuffer.available() > minBufLen) {
-                    // Decrypt the first bytes
+                    // Decrypt the first bytes so we can extract the packet 
length
                     if ((inCipher != null) && (!etmMode)) {
                         inCipher.update(decoderBuffer.array(), 0, 
inCipherSize);
 
@@ -1188,7 +1222,8 @@ public abstract class AbstractSession extends 
SessionHelper {
                     if ((decoderLength < SshConstants.SSH_PACKET_HEADER_LEN)
                             || (decoderLength > (8 * 
SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT))) {
                         log.warn("decode({}) Error decoding packet(invalid 
length): {}", this, decoderLength);
-                        decoderBuffer.dumpHex(getSimplifiedLogger(), 
Level.FINEST, "decode(" + this + ") invalid length packet", this);
+                        decoderBuffer.dumpHex(getSimplifiedLogger(), 
Level.FINEST,
+                            "decode(" + this + ") invalid length packet", 
this);
                         throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR,
                                 "Invalid packet length: " + decoderLength);
                     }
@@ -1202,7 +1237,7 @@ public abstract class AbstractSession extends 
SessionHelper {
             } else if (decoderState == 1) {
                 // The read position should always be after reading the packet 
length at this point
                 assert decoderBuffer.rpos() == Integer.BYTES;
-                int macSize = (inMac != null) ? inMac.getBlockSize() : 0;
+                int macSize = (inMac != null) ? inMacSize : 0;
                 // Check if the packet has been fully received
                 if (decoderBuffer.available() >= (decoderLength + macSize)) {
                     byte[] data = decoderBuffer.array();
@@ -1210,18 +1245,21 @@ public abstract class AbstractSession extends 
SessionHelper {
                         validateIncomingMac(data, 0, decoderLength + 
Integer.BYTES);
 
                         if (inCipher != null) {
-                            inCipher.update(data, Integer.BYTES, 
decoderLength);
+                            inCipher.update(data, Integer.BYTES /* packet 
length is unencrypted */, decoderLength);
 
-                            int blocksCount = decoderLength / 
inCipher.getCipherBlockSize();
+                            int blocksCount = decoderLength / inCipherSize;
                             inBlocksCount.addAndGet(Math.max(1, blocksCount));
                         }
                     } else {
-                        // Decrypt the remaining of the packet
+                        /*
+                         * Decrypt the remaining of the packet - skip the 
block we
+                         * already decoded in order to extract the packet 
length
+                         */
                         if (inCipher != null) {
                             int updateLen = decoderLength + Integer.BYTES - 
inCipherSize;
                             inCipher.update(data, inCipherSize, updateLen);
 
-                            int blocksCount = updateLen / 
inCipher.getCipherBlockSize();
+                            int blocksCount = updateLen / inCipherSize;
                             inBlocksCount.addAndGet(Math.max(1, blocksCount));
                         }
 
@@ -1230,6 +1268,7 @@ public abstract class AbstractSession extends 
SessionHelper {
 
                     // Increment incoming packet sequence number
                     seqi = (seqi + 1L) & 0x0ffffffffL;
+
                     // Get padding
                     int pad = decoderBuffer.getUByte();
                     Buffer packet;
@@ -1253,14 +1292,17 @@ public abstract class AbstractSession extends 
SessionHelper {
                     }
 
                     if (log.isTraceEnabled()) {
-                        packet.dumpHex(getSimplifiedLogger(), Level.FINEST, 
"decode(" + this + ") packet #" + seqi, this);
+                        packet.dumpHex(getSimplifiedLogger(), Level.FINEST,
+                            "decode(" + this + ") packet #" + seqi, this);
                     }
 
-                    // Update stats
+                    // Update counters used to track re-keying
                     inPacketsCount.incrementAndGet();
                     inBytesCount.addAndGet(packet.available());
+
                     // Process decoded packet
                     handleMessage(packet);
+
                     // Set ready to handle next packet
                     decoderBuffer.rpos(decoderLength + Integer.BYTES + 
macSize);
                     decoderBuffer.wpos(wpos);
@@ -1287,7 +1329,7 @@ public abstract class AbstractSession extends 
SessionHelper {
         inMac.doFinal(inMacResult, 0);
 
         // Check the computed result with the received mac (just after the 
packet data)
-        if (!BufferUtils.equals(inMacResult, 0, data, offset + len, 
inMac.getBlockSize())) {
+        if (!BufferUtils.equals(inMacResult, 0, data, offset + len, 
inMacSize)) {
             throw new SshException(SshConstants.SSH2_DISCONNECT_MAC_ERROR, 
"MAC Error");
         }
     }
@@ -1475,7 +1517,8 @@ public abstract class AbstractSession extends 
SessionHelper {
 
         boolean serverSession = isServerSession();
         String value = getNegotiatedKexParameter(KexProposalOption.S2CENC);
-        Cipher s2ccipher = 
ValidateUtils.checkNotNull(NamedFactory.create(getCipherFactories(), value), 
"Unknown s2c cipher: %s", value);
+        Cipher s2ccipher = ValidateUtils.checkNotNull(
+            NamedFactory.create(getCipherFactories(), value), "Unknown s2c 
cipher: %s", value);
         e_s2c = resizeKey(e_s2c, s2ccipher.getKdfSize(), hash, k, h);
         s2ccipher.init(serverSession ? Cipher.Mode.Encrypt : 
Cipher.Mode.Decrypt, e_s2c, iv_s2c);
 
@@ -1494,7 +1537,8 @@ public abstract class AbstractSession extends 
SessionHelper {
         }
 
         value = getNegotiatedKexParameter(KexProposalOption.C2SENC);
-        Cipher c2scipher = 
ValidateUtils.checkNotNull(NamedFactory.create(getCipherFactories(), value), 
"Unknown c2s cipher: %s", value);
+        Cipher c2scipher = ValidateUtils.checkNotNull(
+            NamedFactory.create(getCipherFactories(), value), "Unknown c2s 
cipher: %s", value);
         e_c2s = resizeKey(e_c2s, c2scipher.getKdfSize(), hash, k, h);
         c2scipher.init(serverSession ? Cipher.Mode.Decrypt : 
Cipher.Mode.Encrypt, e_c2s, iv_c2s);
 
@@ -1527,22 +1571,26 @@ public abstract class AbstractSession extends 
SessionHelper {
             inMac = s2cmac;
             inCompression = s2ccomp;
         }
-        outCipherSize = outCipher.getIVSize();
+
+        outCipherSize = outCipher.getCipherBlockSize();
+        outMacSize = outMac.getBlockSize();
         // TODO add support for configurable compression level
         outCompression.init(Compression.Type.Deflater, -1);
 
-        inCipherSize = inCipher.getIVSize();
-        inMacResult = new byte[inMac.getBlockSize()];
+        inCipherSize = inCipher.getCipherBlockSize();
+        inMacSize = inMac.getBlockSize();
+        inMacResult = new byte[inMacSize];
         // TODO add support for configurable compression level
         inCompression.init(Compression.Type.Inflater, -1);
 
         // see https://tools.ietf.org/html/rfc4344#section-3.2
-        int inBlockSize = inCipher.getCipherBlockSize();
-        int outBlockSize = outCipher.getCipherBlockSize();
         // select the lowest cipher size
-        int avgCipherBlockSize = Math.min(inBlockSize, outBlockSize);
-        long recommendedByteRekeyBlocks = 1L << Math.min((avgCipherBlockSize * 
Byte.SIZE) / 4, 63);    // in case (block-size / 4) > 63
-        
maxRekeyBlocks.set(this.getLongProperty(FactoryManager.REKEY_BLOCKS_LIMIT, 
recommendedByteRekeyBlocks));
+        int avgCipherBlockSize = Math.min(inCipherSize, outCipherSize);
+        long recommendedByteRekeyBlocks =
+            1L << Math.min((avgCipherBlockSize * Byte.SIZE) / 4, 63);    // in 
case (block-size / 4) > 63
+        long effectiveRekyBlocksCount =
+            getLongProperty(FactoryManager.REKEY_BLOCKS_LIMIT, 
recommendedByteRekeyBlocks);
+        maxRekeyBlocks.set(effectiveRekyBlocksCount);
         if (debugEnabled) {
             log.debug("receiveNewKeys({}) inCipher={}, outCipher={}, 
recommended blocks limit={}, actual={}",
                   this, inCipher, outCipher, recommendedByteRekeyBlocks, 
maxRekeyBlocks);
@@ -1984,7 +2032,8 @@ public abstract class AbstractSession extends 
SessionHelper {
             return false;   // disabled
         }
 
-        boolean rekey = (inPacketsCount.get() > maxRekyPackets) || 
(outPacketsCount.get() > maxRekyPackets);
+        boolean rekey = (inPacketsCount.get() > maxRekyPackets)
+            || (outPacketsCount.get() > maxRekyPackets);
         if (rekey) {
             if (log.isDebugEnabled()) {
                 log.debug("isRekeyPacketCountsExceeded({}) re-keying: in={}, 
out={}, max={}",
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
index 3a24647..d18d803 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
@@ -687,7 +687,9 @@ public abstract class SessionHelper extends 
AbstractKexFactoryManager implements
      * @return the resized key
      * @throws Exception if a problem occur while resizing the key
      */
-    protected byte[] resizeKey(byte[] e, int kdfSize, Digest hash, byte[] k, 
byte[] h) throws Exception {
+    protected byte[] resizeKey(
+            byte[] e, int kdfSize, Digest hash, byte[] k, byte[] h)
+                throws Exception {
         for (Buffer buffer = null; kdfSize > e.length; buffer = 
BufferUtils.clear(buffer)) {
             if (buffer == null) {
                 buffer = new ByteArrayBuffer();
@@ -697,12 +699,14 @@ public abstract class SessionHelper extends 
AbstractKexFactoryManager implements
             buffer.putRawBytes(h);
             buffer.putRawBytes(e);
             hash.update(buffer.array(), 0, buffer.available());
+
             byte[] foo = hash.digest();
             byte[] bar = new byte[e.length + foo.length];
             System.arraycopy(e, 0, bar, 0, e.length);
             System.arraycopy(foo, 0, bar, e.length, foo.length);
             e = bar;
         }
+
         return e;
     }
 
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
index 394fff5..efb7f60 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
@@ -201,7 +201,8 @@ public class BuiltinCiphersTest extends BaseTestSupport {
     @Test
     public void testParseCiphersList() {
         List<String> builtin = 
NamedResource.getNameList(BuiltinCiphers.VALUES);
-        List<String> unknown = 
Arrays.asList(getClass().getPackage().getName(), getClass().getSimpleName(), 
getCurrentTestName());
+        List<String> unknown = Arrays.asList(
+            getClass().getPackage().getName(), getClass().getSimpleName(), 
getCurrentTestName());
         Random rnd = new Random();
         for (int index = 0; index < (builtin.size() + unknown.size()); 
index++) {
             Collections.shuffle(builtin, rnd);
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
index 949e3df..e9c3d09 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
@@ -71,10 +71,13 @@ public class CipherTest extends BaseTestSupport {
         Collections.unmodifiableList(
             Arrays.asList(
                 new Object[]{BuiltinCiphers.aes128cbc, 
com.jcraft.jsch.jce.AES128CBC.class, NUM_LOADTEST_ROUNDS},
+                new Object[]{BuiltinCiphers.aes128ctr, 
com.jcraft.jsch.jce.AES128CTR.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.tripledescbc, 
com.jcraft.jsch.jce.TripleDESCBC.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.blowfishcbc, 
com.jcraft.jsch.jce.BlowfishCBC.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.aes192cbc, 
com.jcraft.jsch.jce.AES192CBC.class, NUM_LOADTEST_ROUNDS},
+                new Object[]{BuiltinCiphers.aes192ctr, 
com.jcraft.jsch.jce.AES192CTR.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.aes256cbc, 
com.jcraft.jsch.jce.AES256CBC.class, NUM_LOADTEST_ROUNDS},
+                new Object[]{BuiltinCiphers.aes256ctr, 
com.jcraft.jsch.jce.AES256CTR.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.arcfour128, 
com.jcraft.jsch.jce.ARCFOUR128.class, NUM_LOADTEST_ROUNDS},
                 new Object[]{BuiltinCiphers.arcfour256, 
com.jcraft.jsch.jce.ARCFOUR256.class, NUM_LOADTEST_ROUNDS}
             ));
@@ -94,7 +97,10 @@ public class CipherTest extends BaseTestSupport {
     private final Class<? extends com.jcraft.jsch.Cipher> jschCipher;
     private final int loadTestRounds;
 
-    public CipherTest(BuiltinCiphers builtInCipher, Class<? extends 
com.jcraft.jsch.Cipher> jschCipher, int loadTestRounds) {
+    public CipherTest(
+            BuiltinCiphers builtInCipher,
+            Class<? extends com.jcraft.jsch.Cipher> jschCipher,
+            int loadTestRounds) {
         this.builtInCipher = builtInCipher;
         this.jschCipher = jschCipher;
         this.loadTestRounds = loadTestRounds;
@@ -126,7 +132,8 @@ public class CipherTest extends BaseTestSupport {
 
     @Test
     public void testBuiltinCipherSession() throws Exception {
-        Assume.assumeTrue("No internal support for " + 
builtInCipher.getName(), builtInCipher.isSupported() && 
checkCipher(jschCipher.getName()));
+        Assume.assumeTrue("No internal support for " + builtInCipher.getName(),
+            builtInCipher.isSupported() && checkCipher(jschCipher.getName()));
         sshd.setCipherFactories(Collections.singletonList(builtInCipher));
         runJschTest(port);
     }
@@ -170,7 +177,9 @@ public class CipherTest extends BaseTestSupport {
         loadTest(builtInCipher, random, loadTestRounds);
     }
 
-    private static void loadTest(NamedFactory<Cipher> factory, Random random, 
int numRounds) throws Exception {
+    private static void loadTest(
+            NamedFactory<Cipher> factory, Random random, int numRounds)
+                throws Exception {
         Cipher cipher = factory.create();
         byte[] key = new byte[cipher.getKdfSize()];
         byte[] iv = new byte[cipher.getIVSize()];
@@ -185,7 +194,10 @@ public class CipherTest extends BaseTestSupport {
             cipher.update(input, 0, input.length);
         }
         long t1 = System.currentTimeMillis();
-        System.err.println(factory.getName() + "[" + numRounds + "]: " + (t1 - 
t0) + " ms");
+        System.err.append(CipherTest.class.getSimpleName())
+            .append(" - ").append(factory.getName())
+            .append('[').append(Integer.toString(numRounds)).append(']')
+            .append(": ").append(Long.toString(t1 - t0)).println(" ms");
     }
 
     static boolean checkCipher(String cipher) {
@@ -197,7 +209,9 @@ public class CipherTest extends BaseTestSupport {
                     new byte[jschCipher.getIVSize()]);
             return true;
         } catch (Exception e) {
-            System.err.println("checkCipher(" + cipher + ") " + 
e.getClass().getSimpleName() + ": " + e.getMessage());
+            System.err.println("checkCipher(" + cipher + ")"
+                + " " + e.getClass().getSimpleName()
+                + ": " + e.getMessage());
             return false;
         }
     }

Reply via email to