-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
306 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,8 @@ static class AlgorithmNames // TODO: rename to KnownNames | |
public static Name Aes128Gcm => new Name(Aes128GcmBytes); | ||
private static readonly byte[] Aes256GcmBytes = "[email protected]"u8.ToArray(); | ||
public static Name Aes256Gcm => new Name(Aes256GcmBytes); | ||
private static readonly byte[] ChaCha20Poly1305Bytes = "[email protected]"u8.ToArray(); | ||
public static Name ChaCha20Poly1305 => new Name(ChaCha20Poly1305Bytes); | ||
|
||
// KDF algorithms: | ||
private static readonly byte[] BCryptBytes = "bcrypt"u8.ToArray(); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// This file is part of Tmds.Ssh which is released under MIT. | ||
// See file LICENSE for full license details. | ||
|
||
using System; | ||
using System.Buffers; | ||
using System.Buffers.Binary; | ||
using System.Security.Cryptography; | ||
|
||
namespace Tmds.Ssh; | ||
|
||
sealed class ChaCha20Poly1305PacketDecoder : ChaCha20Poly1305PacketEncDecBase, IPacketDecoder | ||
{ | ||
private readonly SequencePool _sequencePool; | ||
private int _currentPacketLength = -1; | ||
|
||
public ChaCha20Poly1305PacketDecoder(SequencePool sequencePool, byte[] key) : | ||
base(key) | ||
{ | ||
_sequencePool = sequencePool; | ||
} | ||
|
||
public void Dispose() | ||
{ } | ||
|
||
public bool TryDecodePacket(Sequence receiveBuffer, uint sequenceNumber, int maxLength, out Packet packet) | ||
{ | ||
packet = new Packet(null); | ||
|
||
// Wait for the length. | ||
if (receiveBuffer.Length < LengthSize) | ||
{ | ||
return false; | ||
} | ||
|
||
// Decrypt length. | ||
int packetLength = _currentPacketLength; | ||
Span<byte> length_unencrypted = stackalloc byte[LengthSize]; | ||
if (packetLength == -1) | ||
{ | ||
ConfigureCiphers(sequenceNumber); | ||
|
||
Span<byte> length_encrypted = stackalloc byte[LengthSize]; | ||
if (receiveBuffer.FirstSpan.Length >= LengthSize) | ||
{ | ||
receiveBuffer.FirstSpan.Slice(0, LengthSize).CopyTo(length_encrypted); | ||
} | ||
else | ||
{ | ||
receiveBuffer.AsReadOnlySequence().Slice(0, LengthSize).CopyTo(length_encrypted); | ||
} | ||
|
||
LengthCipher.ProcessBytes(length_encrypted, length_unencrypted); | ||
|
||
// Verify the packet length isn't too long and properly padded. | ||
uint packet_length = BinaryPrimitives.ReadUInt32BigEndian(length_unencrypted); | ||
if (packet_length > maxLength || (packet_length % PaddTo) != 0) | ||
{ | ||
ThrowHelper.ThrowProtocolPacketTooLong(); | ||
} | ||
|
||
_currentPacketLength = packetLength = (int)packet_length; | ||
} | ||
else | ||
{ | ||
BinaryPrimitives.WriteInt32BigEndian(length_unencrypted, _currentPacketLength); | ||
} | ||
|
||
// Wait for the full encrypted packet. | ||
int total_length = LengthSize + packetLength + TagSize; | ||
if (receiveBuffer.Length < total_length) | ||
{ | ||
return false; | ||
} | ||
|
||
// Check the mac. | ||
ReadOnlySequence<byte> receiveBufferROSequence = receiveBuffer.AsReadOnlySequence(); | ||
ReadOnlySequence<byte> hashed = receiveBufferROSequence.Slice(0, LengthSize + packetLength); | ||
Span<byte> packetTag = stackalloc byte[TagSize]; | ||
receiveBufferROSequence.Slice(LengthSize + packetLength, TagSize).CopyTo(packetTag); | ||
if (hashed.IsSingleSegment) | ||
{ | ||
Mac.BlockUpdate(hashed.FirstSpan); | ||
} | ||
else | ||
{ | ||
foreach (var memory in hashed) | ||
{ | ||
Mac.BlockUpdate(memory.Span); | ||
} | ||
} | ||
Span<byte> tag = stackalloc byte[TagSize]; | ||
Mac.DoFinal(tag); | ||
if (!CryptographicOperations.FixedTimeEquals(packetTag, tag)) | ||
{ | ||
throw new CryptographicException(); | ||
} | ||
|
||
int decodedLength = total_length - TagSize; | ||
Sequence decoded = _sequencePool.RentSequence(); | ||
Span<byte> dst = decoded.AllocGetSpan(decodedLength); | ||
|
||
// Decrypt length. | ||
length_unencrypted.CopyTo(dst); | ||
|
||
// Decrypt payload. | ||
Span<byte> plaintext = dst.Slice(LengthSize, packetLength); | ||
ReadOnlySequence<byte> ciphertext = receiveBufferROSequence.Slice(LengthSize, packetLength); | ||
if (ciphertext.IsSingleSegment) | ||
{ | ||
PayloadCipher.ProcessBytes(ciphertext.FirstSpan, plaintext); | ||
} | ||
else | ||
{ | ||
foreach (var memory in ciphertext) | ||
{ | ||
PayloadCipher.ProcessBytes(memory.Span, plaintext); | ||
plaintext = plaintext.Slice(memory.Length); | ||
} | ||
} | ||
|
||
decoded.AppendAlloced(decodedLength); | ||
packet = new Packet(decoded); | ||
|
||
receiveBuffer.Remove(total_length); | ||
|
||
_currentPacketLength = -1; // start decoding a new packet | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// This file is part of Tmds.Ssh which is released under MIT. | ||
// See file LICENSE for full license details. | ||
|
||
using System; | ||
using System.Buffers.Binary; | ||
using Org.BouncyCastle.Crypto.Engines; | ||
using Org.BouncyCastle.Crypto.Macs; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
|
||
namespace Tmds.Ssh; | ||
|
||
class ChaCha20Poly1305PacketEncDecBase | ||
{ | ||
public const int TagSize = 16; // Poly1305 hash length. | ||
protected const int PaddTo = 8; // We're not a block cipher. Padd to 8 octets per rfc4253. | ||
protected const int LengthSize = 4; // SSH packet length field is 4 bytes. | ||
|
||
protected readonly ChaCha7539Engine LengthCipher; | ||
protected readonly ChaCha7539Engine PayloadCipher; | ||
protected readonly Poly1305 Mac; | ||
private readonly byte[] _K1; | ||
private readonly byte[] _K2; | ||
|
||
protected ChaCha20Poly1305PacketEncDecBase(byte[] key) | ||
{ | ||
_K1 = key.AsSpan(32, 32).ToArray(); | ||
_K2 = key.AsSpan(0, 32).ToArray(); | ||
LengthCipher = new(); | ||
PayloadCipher = new(); | ||
Mac = new(); | ||
} | ||
|
||
protected void ConfigureCiphers(uint sequenceNumber) | ||
{ | ||
Span<byte> iv = stackalloc byte[12]; | ||
Span<byte> polyKey = stackalloc byte[64]; | ||
BinaryPrimitives.WriteUInt64BigEndian(iv[4..], sequenceNumber); | ||
LengthCipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(_K1), iv)); | ||
PayloadCipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(_K2), iv)); | ||
// note: encrypting 64 bytes increments the ChaCha20 block counter. | ||
PayloadCipher.ProcessBytes(input: polyKey, output: polyKey); | ||
Mac.Init(new KeyParameter(polyKey[..32])); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// This file is part of Tmds.Ssh which is released under MIT. | ||
// See file LICENSE for full license details. | ||
|
||
using System; | ||
using System.Buffers; | ||
|
||
namespace Tmds.Ssh; | ||
|
||
// https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD | ||
sealed class ChaCha20Poly1305PacketEncoder : ChaCha20Poly1305PacketEncDecBase, IPacketEncoder | ||
{ | ||
public ChaCha20Poly1305PacketEncoder(byte[] key) : | ||
base(key) | ||
{ } | ||
|
||
public void Dispose() | ||
{ } | ||
|
||
public void Encode(uint sequenceNumber, Packet packet, Sequence output) | ||
{ | ||
using var pkt = packet.Move(); // Dispose the packet. | ||
|
||
ConfigureCiphers(sequenceNumber); | ||
|
||
// Padding. | ||
uint payload_length = (uint)pkt.PayloadLength; | ||
// PT (Plain Text) | ||
// byte padding_length; // 4 <= padding_length < 256 | ||
// byte[n1] payload; // n1 = packet_length-padding_length-1 | ||
// byte[n2] random_padding; // n2 = padding_length | ||
byte padding_length = IPacketEncoder.DeterminePaddingLength(payload_length + 1, multipleOf: PaddTo); | ||
pkt.WriteHeaderAndPadding(padding_length); | ||
|
||
var unencrypted_packet = pkt.AsReadOnlySequence(); | ||
ReadOnlySpan<byte> packet_length = unencrypted_packet.FirstSpan.Slice(0, LengthSize); // packet_length | ||
ReadOnlySequence<byte> pt = unencrypted_packet.Slice(LengthSize); // PT (Plain Text) | ||
|
||
int textLength = (int)pt.Length; | ||
int encodedLength = LengthSize + textLength + TagSize; | ||
Span<byte> dst = output.AllocGetSpan(encodedLength); | ||
|
||
// Encrypt length. | ||
Span<byte> length_encrypted = dst.Slice(0, LengthSize); | ||
LengthCipher.ProcessBytes(packet_length, length_encrypted); | ||
|
||
// Encrypt payload. | ||
Span<byte> ciphertext = dst.Slice(LengthSize, textLength); | ||
if (pt.IsSingleSegment) | ||
{ | ||
PayloadCipher.ProcessBytes(pt.FirstSpan, ciphertext); | ||
} | ||
else | ||
{ | ||
foreach (var memory in pt) | ||
{ | ||
PayloadCipher.ProcessBytes(memory.Span, ciphertext); | ||
ciphertext = ciphertext.Slice(memory.Length); | ||
} | ||
} | ||
|
||
// Mac. | ||
Span<byte> tag = dst.Slice(LengthSize + textLength, TagSize); | ||
Mac.BlockUpdate(dst.Slice(0, LengthSize + textLength)); | ||
Mac.DoFinal(tag); | ||
|
||
output.AppendAlloced(encodedLength); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,8 +26,8 @@ public void Defaults() | |
Assert.Equal(new[] { new Name("ecdh-sha2-nistp256"), new Name("ecdh-sha2-nistp384"), new Name("ecdh-sha2-nistp521") }, settings.KeyExchangeAlgorithms); | ||
Assert.Equal(new[] { new Name("ecdsa-sha2-nistp521"), new Name("ecdsa-sha2-nistp384"), new Name("ecdsa-sha2-nistp256"), new Name("rsa-sha2-512"), new Name("rsa-sha2-256") }, settings.ServerHostKeyAlgorithms); | ||
Assert.Equal(new[] { new Name("ssh-ed25519"), new Name("ecdsa-sha2-nistp521"), new Name("ecdsa-sha2-nistp384"), new Name("ecdsa-sha2-nistp256"), new Name("rsa-sha2-512"), new Name("rsa-sha2-256") }, settings.PublicKeyAcceptedAlgorithms); | ||
Assert.Equal(new[] { new Name("[email protected]"), new Name("[email protected]") }, settings.EncryptionAlgorithmsClientToServer); | ||
Assert.Equal(new[] { new Name("[email protected]"), new Name("[email protected]") }, settings.EncryptionAlgorithmsServerToClient); | ||
Assert.Equal(new[] { new Name("[email protected]"), new Name("[email protected]"), new Name("[email protected]") }, settings.EncryptionAlgorithmsClientToServer); | ||
Assert.Equal(new[] { new Name("[email protected]"), new Name("[email protected]"), new Name("[email protected]") }, settings.EncryptionAlgorithmsServerToClient); | ||
Assert.Equal(Array.Empty<Name>(), settings.MacAlgorithmsClientToServer); | ||
Assert.Equal(Array.Empty<Name>(), settings.MacAlgorithmsServerToClient); | ||
Assert.Equal(new[] { new Name("none") }, settings.CompressionAlgorithmsClientToServer); | ||
|
Oops, something went wrong.