diff --git a/README.md b/README.md
index 84e665bd9..e8b6c68f3 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@ the missing test once you figure things out. 🤓
* aes128-cbc
* aes192-cbc
* aes256-cbc
+* aes128-gcm@openssh.com (.NET Standard 2.1, .NET 6 and higher)
+* aes256-gcm@openssh.com (.NET Standard 2.1, .NET 6 and higher)
* blowfish-cbc
* twofish-cbc
* twofish192-cbc
diff --git a/src/Renci.SshNet/CipherInfo.cs b/src/Renci.SshNet/CipherInfo.cs
index 2c9832a19..f8d94c5f4 100644
--- a/src/Renci.SshNet/CipherInfo.cs
+++ b/src/Renci.SshNet/CipherInfo.cs
@@ -17,6 +17,14 @@ public class CipherInfo
///
public int KeySize { get; private set; }
+ ///
+ /// Gets a value indicating whether the cipher is AEAD (Authenticated Encryption with Associated data).
+ ///
+ ///
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
+ ///
+ public bool IsAead { get; private set; }
+
///
/// Gets the cipher.
///
@@ -27,10 +35,12 @@ public class CipherInfo
///
/// Size of the key.
/// The cipher.
- public CipherInfo(int keySize, Func cipher)
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
+ public CipherInfo(int keySize, Func cipher, bool isAead = false)
{
KeySize = keySize;
Cipher = (key, iv) => cipher(key.Take(KeySize / 8), iv);
+ IsAead = isAead;
}
}
}
diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs
index 7584816ff..358f8b699 100644
--- a/src/Renci.SshNet/ConnectionInfo.cs
+++ b/src/Renci.SshNet/ConnectionInfo.cs
@@ -388,6 +388,10 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
{ "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
{ "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
{ "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER
+ { "aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv), isAead: true) },
+ { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv), isAead: true) },
+#endif
{ "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "blowfish-cbc", new CipherInfo(128, (key, iv) => new BlowfishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "twofish-cbc", new CipherInfo(256, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)) },
diff --git a/src/Renci.SshNet/Messages/Message.cs b/src/Renci.SshNet/Messages/Message.cs
index fa3ba5f72..b5c43acaf 100644
--- a/src/Renci.SshNet/Messages/Message.cs
+++ b/src/Renci.SshNet/Messages/Message.cs
@@ -37,7 +37,7 @@ protected override void WriteBytes(SshDataStream stream)
base.WriteBytes(stream);
}
- internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool isEncryptThenMAC = false)
+ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool excludePacketLengthFieldWhenPadding = false)
{
const int outboundPacketSequenceSize = 4;
@@ -78,9 +78,9 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool is
var packetLength = messageLength + 4 + 1;
// determine the padding length
- // in Encrypt-then-MAC mode, the length field is not encrypted, so we should keep it out of the
+ // in Encrypt-then-MAC mode or AEAD, the length field is not encrypted, so we should keep it out of the
// padding length calculation
- var paddingLength = GetPaddingLength(paddingMultiplier, isEncryptThenMAC ? packetLength - 4 : packetLength);
+ var paddingLength = GetPaddingLength(paddingMultiplier, excludePacketLengthFieldWhenPadding ? packetLength - 4 : packetLength);
// add padding bytes
var paddingBytes = new byte[paddingLength];
@@ -106,9 +106,9 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool is
var packetLength = messageLength + 4 + 1;
// determine the padding length
- // in Encrypt-then-MAC mode, the length field is not encrypted, so we should keep it out of the
+ // in Encrypt-then-MAC mode or AEAD, the length field is not encrypted, so we should keep it out of the
// padding length calculation
- var paddingLength = GetPaddingLength(paddingMultiplier, isEncryptThenMAC ? packetLength - 4 : packetLength);
+ var paddingLength = GetPaddingLength(paddingMultiplier, excludePacketLengthFieldWhenPadding ? packetLength - 4 : packetLength);
var packetDataLength = GetPacketDataLength(messageLength, paddingLength);
diff --git a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs b/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
index b9f7dde58..3e7e6541a 100644
--- a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
@@ -110,18 +110,6 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
return output;
}
- ///
- /// Decrypts the specified data.
- ///
- /// The data.
- ///
- /// The decrypted data.
- ///
- public override byte[] Decrypt(byte[] input)
- {
- return Decrypt(input, 0, input.Length);
- }
-
///
/// Decrypts the specified input.
///
@@ -167,5 +155,31 @@ public override byte[] Decrypt(byte[] input, int offset, int length)
return output;
}
+
+ ///
+ /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
+ ///
+ /// The input data to encrypt.
+ /// The offset into the input byte array from which to begin using data.
+ /// The number of bytes in the input byte array to use as data.
+ /// The output to which to write encrypted data.
+ /// The offset into the output byte array from which to begin writing data.
+ ///
+ /// The number of bytes encrypted.
+ ///
+ public abstract int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
+
+ ///
+ /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
+ ///
+ /// The input data to decrypt.
+ /// The offset into the input byte array from which to begin using data.
+ /// The number of bytes in the input byte array to use as data.
+ /// The output to which to write decrypted data.
+ /// The offset into the output byte array from which to begin writing data.
+ ///
+ /// The number of bytes decrypted.
+ ///
+ public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
}
}
diff --git a/src/Renci.SshNet/Security/Cryptography/Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Cipher.cs
index e624bbba3..7fb5ee111 100644
--- a/src/Renci.SshNet/Security/Cryptography/Cipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Cipher.cs
@@ -13,6 +13,14 @@ public abstract class Cipher
///
public abstract byte MinimumSize { get; }
+ ///
+ /// Gets the tag (MAC) size.
+ ///
+ ///
+ /// The tag (MAC) size.
+ ///
+ public virtual int TagSize { get; }
+
///
/// Encrypts the specified input.
///
@@ -41,7 +49,10 @@ public byte[] Encrypt(byte[] input)
///
/// The decrypted data.
///
- public abstract byte[] Decrypt(byte[] input);
+ public byte[] Decrypt(byte[] input)
+ {
+ return Decrypt(input, 0, input.Length);
+ }
///
/// Decrypts the specified input.
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs
index 51ebfdd14..9f948b3cf 100644
--- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs
@@ -5,22 +5,22 @@
///
public enum AesCipherMode
{
- /// CBC Mode.
+ /// Cipher Block Chain Mode.
CBC = 1,
- /// ECB Mode.
+ /// Electronic Codebook Mode.
ECB = 2,
- /// OFB Mode.
+ /// Output Feedback Mode.
OFB = 3,
- /// CFB Mode.
+ /// Cipher Feedback Mode.
CFB = 4,
- /// CTS Mode.
+ /// Cipher Text Stealing Mode.
CTS = 5,
- /// CTR Mode.
+ /// Counter Mode.
CTR = 6
}
}
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
new file mode 100644
index 000000000..ea64efcba
--- /dev/null
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
@@ -0,0 +1,137 @@
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER
+using System;
+using System.Buffers.Binary;
+using System.Security.Cryptography;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Security.Cryptography.Ciphers
+{
+ ///
+ /// AES GCM cipher implementation.
+ /// .
+ ///
+ public sealed class AesGcmCipher : SymmetricCipher, IDisposable
+ {
+ private readonly byte[] _nonce;
+ private readonly AesGcm _aesGcm;
+
+ ///
+ public override byte MinimumSize
+ {
+ get
+ {
+ return 16;
+ }
+ }
+
+ ///
+ public override int TagSize
+ {
+ get
+ {
+ return 16;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The key.
+ /// The IV.
+ public AesGcmCipher(byte[] key, byte[] iv)
+ : base(key)
+ {
+ _nonce = iv.Take(12);
+#if NET8_0_OR_GREATER
+ _aesGcm = new AesGcm(key, TagSize);
+#else
+ _aesGcm = new AesGcm(key);
+#endif
+ }
+
+ ///
+ /// Encrypts the specified input.
+ ///
+ /// The input.
+ /// The zero-based offset in at which to begin encrypting.
+ /// The number of bytes to encrypt from .
+ ///
+ /// The packet length field + cipher text + tag.
+ ///
+ public override byte[] Encrypt(byte[] input, int offset, int length)
+ {
+ // [outbound sequence field][packet length field][padding length field sz][payload][random paddings]
+ // [----4 bytes----(offset)][------4 bytes------][----------------Plain Text---------------(length)]
+ var packetLengthField = new ReadOnlySpan(input, offset, 4);
+ var plainText = new ReadOnlySpan(input, offset + 4, length - 4);
+
+ var output = new byte[length + TagSize];
+ packetLengthField.CopyTo(output);
+ var cipherText = new Span(output, 4, length - 4);
+ var tag = new Span(output, length, TagSize);
+
+ _aesGcm.Encrypt(_nonce, plainText, cipherText, tag, packetLengthField);
+
+ IncrementCounter();
+
+ return output;
+ }
+
+ ///
+ /// Decrypts the specified input.
+ ///
+ /// The input.
+ /// The zero-based offset in at which to begin decrypting and authenticating.
+ /// The number of bytes to decrypt and authenticate from .
+ ///
+ /// The packet length field + plain text.
+ ///
+ public override byte[] Decrypt(byte[] input, int offset, int length)
+ {
+ // [inbound sequence field][packet length field][padding length field sz][payload][random paddings][Authenticated TAG]
+ // [----4 bytes---(offset)][------4 bytes------][------------------Cipher Text--------------------][---TAG---(length)]
+ var packetLengthField = new ReadOnlySpan(input, offset, 4);
+ var cipherText = new ReadOnlySpan(input, offset + 4, length - 4 - TagSize);
+ var tag = new ReadOnlySpan(input, offset + length - TagSize, TagSize);
+
+ var output = new byte[length - TagSize];
+ packetLengthField.CopyTo(output);
+ var plainText = new Span(output, 4, length - 4 - TagSize);
+
+ _aesGcm.Decrypt(_nonce, cipherText, tag, plainText, packetLengthField);
+
+ IncrementCounter();
+
+ return output;
+ }
+
+ private void IncrementCounter()
+ {
+ var invocationCounter = new Span(_nonce, 4, 8);
+ var count = BinaryPrimitives.ReadUInt64BigEndian(invocationCounter);
+ BinaryPrimitives.WriteUInt64BigEndian(invocationCounter, count + 1);
+ }
+
+ ///
+ /// Dispose the instance.
+ ///
+ /// Set to True to dispose of resouces.
+ public void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _aesGcm.Dispose();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+#endif
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs
index 41387ee02..aed9683b8 100644
--- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs
@@ -50,38 +50,6 @@ public Arc4Cipher(byte[] key, bool dischargeFirstBytes)
}
}
- ///
- /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
- ///
- /// The input data to encrypt.
- /// The offset into the input byte array from which to begin using data.
- /// The number of bytes in the input byte array to use as data.
- /// The output to which to write encrypted data.
- /// The offset into the output byte array from which to begin writing data.
- ///
- /// The number of bytes encrypted.
- ///
- public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
- {
- return ProcessBytes(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
- }
-
- ///
- /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
- ///
- /// The input data to decrypt.
- /// The offset into the input byte array from which to begin using data.
- /// The number of bytes in the input byte array to use as data.
- /// The output to which to write decrypted data.
- /// The offset into the output byte array from which to begin writing data.
- ///
- /// The number of bytes decrypted.
- ///
- public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
- {
- return ProcessBytes(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
- }
-
///
/// Encrypts the specified input.
///
@@ -98,18 +66,6 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
return output;
}
- ///
- /// Decrypts the specified input.
- ///
- /// The input.
- ///
- /// The decrypted data.
- ///
- public override byte[] Decrypt(byte[] input)
- {
- return Decrypt(input, 0, input.Length);
- }
-
///
/// Decrypts the specified input.
///
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs
index 8cb58a93e..acfb3fc9a 100644
--- a/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs
@@ -49,20 +49,6 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
return Transform(paddedBlock);
}
- ///
- /// Decrypts the specified data.
- ///
- /// The data.
- ///
- /// The decrypted data.
- ///
- /// Only block type 01 or 02 are supported.
- /// Thrown when decrypted block type is not supported.
- public override byte[] Decrypt(byte[] input)
- {
- return Decrypt(input, 0, input.Length);
- }
-
///
/// Decrypts the specified input.
///
diff --git a/src/Renci.SshNet/Security/Cryptography/SymmetricCipher.cs b/src/Renci.SshNet/Security/Cryptography/SymmetricCipher.cs
index 33140bd9c..ee2c239f0 100644
--- a/src/Renci.SshNet/Security/Cryptography/SymmetricCipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/SymmetricCipher.cs
@@ -26,31 +26,5 @@ protected SymmetricCipher(byte[] key)
Key = key;
}
-
- ///
- /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
- ///
- /// The input data to encrypt.
- /// The offset into the input byte array from which to begin using data.
- /// The number of bytes in the input byte array to use as data.
- /// The output to which to write encrypted data.
- /// The offset into the output byte array from which to begin writing data.
- ///
- /// The number of bytes encrypted.
- ///
- public abstract int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
-
- ///
- /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
- ///
- /// The input data to decrypt.
- /// The offset into the input byte array from which to begin using data.
- /// The number of bytes in the input byte array to use as data.
- /// The output to which to write decrypted data.
- /// The offset into the output byte array from which to begin writing data.
- ///
- /// The number of bytes decrypted.
- ///
- public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
}
}
diff --git a/src/Renci.SshNet/Security/IKeyExchange.cs b/src/Renci.SshNet/Security/IKeyExchange.cs
index c8f04b219..7f2e349d5 100644
--- a/src/Renci.SshNet/Security/IKeyExchange.cs
+++ b/src/Renci.SshNet/Security/IKeyExchange.cs
@@ -50,18 +50,20 @@ public interface IKeyExchange : IDisposable
///
/// Creates the client-side cipher to use.
///
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
///
/// The client cipher.
///
- Cipher CreateClientCipher();
+ Cipher CreateClientCipher(out bool isAead);
///
/// Creates the server-side cipher to use.
///
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
///
/// The server cipher.
///
- Cipher CreateServerCipher();
+ Cipher CreateServerCipher(out bool isAead);
///
/// Creates the server-side hash algorithm to use.
diff --git a/src/Renci.SshNet/Security/KeyExchange.cs b/src/Renci.SshNet/Security/KeyExchange.cs
index 10f7e0f8a..e444059e7 100644
--- a/src/Renci.SshNet/Security/KeyExchange.cs
+++ b/src/Renci.SshNet/Security/KeyExchange.cs
@@ -83,6 +83,7 @@ from a in message.EncryptionAlgorithmsClientToServer
}
session.ConnectionInfo.CurrentClientEncryption = clientEncryptionAlgorithmName;
+ _clientCipherInfo = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
// Determine encryption algorithm
var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
@@ -95,30 +96,39 @@ from a in message.EncryptionAlgorithmsServerToClient
}
session.ConnectionInfo.CurrentServerEncryption = serverDecryptionAlgorithmName;
+ _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName];
- // Determine client hmac algorithm
- var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
- from a in message.MacAlgorithmsClientToServer
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(clientHmacAlgorithmName))
+ if (!_clientCipherInfo.IsAead)
{
- throw new SshConnectionException("Client HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
+ // Determine client hmac algorithm
+ var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
+ from a in message.MacAlgorithmsClientToServer
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(clientHmacAlgorithmName))
+ {
+ throw new SshConnectionException("Client HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
+ }
- session.ConnectionInfo.CurrentClientHmacAlgorithm = clientHmacAlgorithmName;
+ session.ConnectionInfo.CurrentClientHmacAlgorithm = clientHmacAlgorithmName;
+ _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
+ }
- // Determine server hmac algorithm
- var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
- from a in message.MacAlgorithmsServerToClient
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(serverHmacAlgorithmName))
+ if (!_serverCipherInfo.IsAead)
{
- throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
+ // Determine server hmac algorithm
+ var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
+ from a in message.MacAlgorithmsServerToClient
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(serverHmacAlgorithmName))
+ {
+ throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
+ }
- session.ConnectionInfo.CurrentServerHmacAlgorithm = serverHmacAlgorithmName;
+ session.ConnectionInfo.CurrentServerHmacAlgorithm = serverHmacAlgorithmName;
+ _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
+ }
// Determine compression algorithm
var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
@@ -131,6 +141,7 @@ from a in message.CompressionAlgorithmsClientToServer
}
session.ConnectionInfo.CurrentClientCompressionAlgorithm = compressionAlgorithmName;
+ _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
// Determine decompression algorithm
var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
@@ -143,12 +154,6 @@ from a in message.CompressionAlgorithmsServerToClient
}
session.ConnectionInfo.CurrentServerCompressionAlgorithm = decompressionAlgorithmName;
-
- _clientCipherInfo = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
- _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName];
- _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
- _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
- _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
_decompressorFactory = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName];
}
@@ -168,9 +173,12 @@ public virtual void Finish()
///
/// Creates the server side cipher to use.
///
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
/// Server cipher.
- public Cipher CreateServerCipher()
+ public Cipher CreateServerCipher(out bool isAead)
{
+ isAead = _serverCipherInfo.IsAead;
+
// Resolve Session ID
var sessionId = Session.SessionId ?? ExchangeHash;
@@ -193,9 +201,12 @@ public Cipher CreateServerCipher()
///
/// Creates the client side cipher to use.
///
+ /// to indicate the cipher is AEAD, to incidicate the cipher is not AEAD.
/// Client cipher.
- public Cipher CreateClientCipher()
+ public Cipher CreateClientCipher(out bool isAead)
{
+ isAead = _clientCipherInfo.IsAead;
+
// Resolve Session ID
var sessionId = Session.SessionId ?? ExchangeHash;
@@ -224,6 +235,12 @@ public Cipher CreateClientCipher()
///
public HashAlgorithm CreateServerHash(out bool isEncryptThenMAC)
{
+ if (_serverHashInfo == null)
+ {
+ isEncryptThenMAC = false;
+ return null;
+ }
+
isEncryptThenMAC = _serverHashInfo.IsEncryptThenMAC;
// Resolve Session ID
@@ -250,6 +267,12 @@ public HashAlgorithm CreateServerHash(out bool isEncryptThenMAC)
///
public HashAlgorithm CreateClientHash(out bool isEncryptThenMAC)
{
+ if (_clientHashInfo == null)
+ {
+ isEncryptThenMAC = false;
+ return null;
+ }
+
isEncryptThenMAC = _clientHashInfo.IsEncryptThenMAC;
// Resolve Session ID
diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs
index 0c067ec2d..e033ec646 100644
--- a/src/Renci.SshNet/Session.cs
+++ b/src/Renci.SshNet/Session.cs
@@ -164,9 +164,13 @@ public class Session : ISession
private bool _clientEtm;
+ private Cipher _serverCipher;
+
private Cipher _clientCipher;
- private Cipher _serverCipher;
+ private bool _serverAead;
+
+ private bool _clientAead;
private Compressor _serverDecompression;
@@ -1041,8 +1045,8 @@ internal void SendMessage(Message message)
DiagnosticAbstraction.Log(string.Format("[{0}] Sending message '{1}' to server: '{2}'.", ToHex(SessionId), message.GetType().Name, message));
- var paddingMultiplier = _clientCipher is null ? (byte) 8 : Math.Max((byte) 8, _serverCipher.MinimumSize);
- var packetData = message.GetPacket(paddingMultiplier, _clientCompression, _clientMac != null && _clientEtm);
+ var paddingMultiplier = _clientCipher is null ? (byte) 8 : Math.Max((byte) 8, _clientCipher.MinimumSize);
+ var packetData = message.GetPacket(paddingMultiplier, _clientCompression, _clientEtm || _clientAead);
// take a write lock to ensure the outbound packet sequence number is incremented
// atomically, and only after the packet has actually been sent
@@ -1051,11 +1055,11 @@ internal void SendMessage(Message message)
byte[] hash = null;
var packetDataOffset = 4; // first four bytes are reserved for outbound packet sequence
+ // write outbound packet sequence to start of packet data
+ Pack.UInt32ToBigEndian(_outboundPacketSequence, packetData);
+
if (_clientMac != null && !_clientEtm)
{
- // write outbound packet sequence to start of packet data
- Pack.UInt32ToBigEndian(_outboundPacketSequence, packetData);
-
// calculate packet hash
hash = _clientMac.ComputeHash(packetData);
}
@@ -1063,7 +1067,7 @@ internal void SendMessage(Message message)
// Encrypt packet data
if (_clientCipher != null)
{
- if (_clientMac != null && _clientEtm)
+ if (_clientEtm)
{
// The length of the "packet length" field in bytes
const int packetLengthFieldLength = 4;
@@ -1072,9 +1076,6 @@ internal void SendMessage(Message message)
Array.Resize(ref packetData, packetDataOffset + packetLengthFieldLength + encryptedData.Length);
- // write outbound packet sequence to start of packet data
- Pack.UInt32ToBigEndian(_outboundPacketSequence, packetData);
-
// write encrypted data
Buffer.BlockCopy(encryptedData, 0, packetData, packetDataOffset + packetLengthFieldLength, encryptedData.Length);
@@ -1205,9 +1206,8 @@ private Message ReceiveMessage(Socket socket)
int blockSize;
- // Determine the size of the first block which is 8 or cipher block size (whichever is larger) bytes
- // The "packet length" field is not encrypted in ETM.
- if (_serverMac != null && _serverEtm)
+ // Determine the size of the first block which is 8 or cipher block size (whichever is larger) bytes, or 4 if "packet length" field is not encrypted
+ if (_serverEtm || _serverAead)
{
blockSize = (byte) 4;
}
@@ -1220,7 +1220,16 @@ private Message ReceiveMessage(Socket socket)
blockSize = (byte) 8;
}
- var serverMacLength = _serverMac != null ? _serverMac.HashSize/8 : 0;
+ var serverMacLength = 0;
+
+ if (_serverAead)
+ {
+ serverMacLength = _serverCipher.TagSize;
+ }
+ else if (_serverMac != null)
+ {
+ serverMacLength = _serverMac.HashSize / 8;
+ }
byte[] data;
uint packetLength;
@@ -1238,7 +1247,7 @@ private Message ReceiveMessage(Socket socket)
return null;
}
- if (_serverCipher != null && (_serverMac == null || !_serverEtm))
+ if (_serverCipher != null && !_serverAead && (_serverMac == null || !_serverEtm))
{
firstBlock = _serverCipher.Decrypt(firstBlock);
}
@@ -1296,11 +1305,23 @@ private Message ReceiveMessage(Socket socket)
if (_serverCipher != null)
{
- var numberOfBytesToDecrypt = data.Length - (blockSize + inboundPacketSequenceLength + serverMacLength);
- if (numberOfBytesToDecrypt > 0)
+ if (_serverAead)
{
- var decryptedData = _serverCipher.Decrypt(data, blockSize + inboundPacketSequenceLength, numberOfBytesToDecrypt);
- Buffer.BlockCopy(decryptedData, 0, data, blockSize + inboundPacketSequenceLength, decryptedData.Length);
+ var numberOfBytesToDecryptAndAuthenticate = data.Length - inboundPacketSequenceLength;
+ if (numberOfBytesToDecryptAndAuthenticate > 0)
+ {
+ var decryptedData = _serverCipher.Decrypt(data, inboundPacketSequenceLength, numberOfBytesToDecryptAndAuthenticate);
+ Buffer.BlockCopy(decryptedData, 0, data, inboundPacketSequenceLength, decryptedData.Length);
+ }
+ }
+ else
+ {
+ var numberOfBytesToDecrypt = data.Length - (blockSize + inboundPacketSequenceLength + serverMacLength);
+ if (numberOfBytesToDecrypt > 0)
+ {
+ var decryptedData = _serverCipher.Decrypt(data, blockSize + inboundPacketSequenceLength, numberOfBytesToDecrypt);
+ Buffer.BlockCopy(decryptedData, 0, data, blockSize + inboundPacketSequenceLength, decryptedData.Length);
+ }
}
}
@@ -1507,10 +1528,12 @@ internal void OnNewKeysReceived(NewKeysMessage message)
}
// Update negotiated algorithms
- _serverCipher = _keyExchange.CreateServerCipher();
- _clientCipher = _keyExchange.CreateClientCipher();
+ _serverCipher = _keyExchange.CreateServerCipher(out _serverAead);
+ _clientCipher = _keyExchange.CreateClientCipher(out _clientAead);
+
_serverMac = _keyExchange.CreateServerHash(out _serverEtm);
_clientMac = _keyExchange.CreateClientHash(out _clientEtm);
+
_clientCompression = _keyExchange.CreateCompressor();
_serverDecompression = _keyExchange.CreateDecompressor();
diff --git a/test/Renci.SshNet.IntegrationTests/CipherTests.cs b/test/Renci.SshNet.IntegrationTests/CipherTests.cs
index 1a11f9814..d60d6e579 100644
--- a/test/Renci.SshNet.IntegrationTests/CipherTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/CipherTests.cs
@@ -64,6 +64,18 @@ public void Aes256Ctr()
DoTest(Cipher.Aes256Ctr);
}
+ [TestMethod]
+ public void Aes128Gcm()
+ {
+ DoTest(Cipher.Aes128Gcm);
+ }
+
+ [TestMethod]
+ public void Aes256Gcm()
+ {
+ DoTest(Cipher.Aes256Gcm);
+ }
+
private void DoTest(Cipher cipher)
{
_remoteSshdConfig.ClearCiphers()
diff --git a/test/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs b/test/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs
index 5ce0d4675..42c1f54a6 100644
--- a/test/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs
+++ b/test/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs
@@ -211,10 +211,18 @@ private void SetupMocks()
_ = _keyExchangeMock.Setup(p => p.Start(Session, It.IsAny(), false));
_ = _keyExchangeMock.Setup(p => p.ExchangeHash)
.Returns(SessionId);
- _ = _keyExchangeMock.Setup(p => p.CreateServerCipher())
- .Returns((Cipher) null);
- _ = _keyExchangeMock.Setup(p => p.CreateClientCipher())
- .Returns((Cipher) null);
+ _ = _keyExchangeMock.Setup(p => p.CreateServerCipher(out It.Ref.IsAny))
+ .Returns((ref bool serverAead) =>
+ {
+ serverAead = false;
+ return (Cipher) null;
+ });
+ _ = _keyExchangeMock.Setup(p => p.CreateClientCipher(out It.Ref.IsAny))
+ .Returns((ref bool clientAead) =>
+ {
+ clientAead = false;
+ return (Cipher) null;
+ });
_ = _keyExchangeMock.Setup(p => p.CreateServerHash(out It.Ref.IsAny))
.Returns((ref bool serverEtm) =>
{
diff --git a/test/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs b/test/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs
index c75fa32a3..b0e714811 100644
--- a/test/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs
+++ b/test/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs
@@ -160,10 +160,18 @@ private void SetupMocks()
_ = _keyExchangeMock.Setup(p => p.Start(Session, It.IsAny(), false));
_ = _keyExchangeMock.Setup(p => p.ExchangeHash)
.Returns(SessionId);
- _ = _keyExchangeMock.Setup(p => p.CreateServerCipher())
- .Returns((Cipher) null);
- _ = _keyExchangeMock.Setup(p => p.CreateClientCipher())
- .Returns((Cipher) null);
+ _ = _keyExchangeMock.Setup(p => p.CreateServerCipher(out It.Ref.IsAny))
+ .Returns((ref bool serverAead) =>
+ {
+ serverAead = false;
+ return (Cipher) null;
+ });
+ _ = _keyExchangeMock.Setup(p => p.CreateClientCipher(out It.Ref.IsAny))
+ .Returns((ref bool clientAead) =>
+ {
+ clientAead = false;
+ return (Cipher) null;
+ });
_ = _keyExchangeMock.Setup(p => p.CreateServerHash(out It.Ref.IsAny))
.Returns((ref bool serverEtm) =>
{